0x03 注入


下面来说一下注入。 这里谈的是mysql。 注入大概也就是把用户可控的一些变量, 带入到了数据库的各种操作当中且没有做好很好的过滤。 比如注册用户的时候检测用户名是否存在的时候,把用户提交的用户名拿到数据库中去查询。 查询是否存在这个用户名, 如果这里对用户名没有做好过滤的话 那么用户就可以提交一些特殊字符来注入了。

现在注入的主要原因是 很多程序员在写sql语句的时候 还是搞的语句拼接。

一些用了预编译或者是在查询的函数中再来过滤 很多时候就给跪了。

select update insert delete

因为mysql query 并不能执行多行语句, 除非pdo啥的能多行 所以不能像mssql那样 还能在select后执行个update管理的语句。

对于这四种类型的注入一般的语句的构造也不同。

如果有mysql error的话

那么这四种就都能用报错注入 这种是比较方便的

如果没mysql error的话

Select 的注入 一般是用union select 如果把对数据库中的查询结果展示出来的话那么就能直接出数据了。 如果无回显的话 那么当然就是盲注了。

Update的注入 如果是在update set的位置的话 那么我们可以找找这个表的哪个column会被展示出来 例如如果一个update的注入点是在用户表且是在set位置可控的话 那么我们可以update email这个column 然后去用户资料看一下自己的email就出数据了 语句例如 update table set email=(select user()) 如果是在where后的话 那么一般也就是盲注了。

Insert 的注入 也是一般是通过找哪个column会不会显示出来 尽量把要出的数据插入到这个column里面去。 如果没显示的话 也是盲注。

Delete的注入 一般都是盲注了。

数字型注入主要就是因为他的变量并没有用单引号引住。

但是基本上都是被强制类型转换了 intval啥的。

但是有时候会有遗漏的嘛。

而字符型和搜索型的 都是会有单引号引住的。

所以需要闭合单引号再来进行注入。

说到单引号不得不说个php.ini里的配置

Magic_quotes_gpc 在稍微高点的版本默认都是on

但是却在应该是5.4就已经废除了。

从字面意思上来看 就是对GPC QUOTE嘛

GPC 对应的就是GET POST COOKIE

会被转义的字符为 ' “ \ NULL 会在前面添加上一个转义符。

导致了失去本来的意义 无法闭合单引号进行注入。

(1) 全局没有做addslashes的

像这种全局没有对GET POST COOKIE 做addslashes的 这种厂商基本是会在查询的时候 再对一些用户可控的变量进行addslashes 甚至是不进行addslashes 直接带入查询的。

这样的就算在查询的时候进行addslashes 在很多时候也都能找到几处遗漏了addslashes的。 这种的比较简单 不多说。

(2) 全局做addslashes

现在稍微好一点的厂商都知道了在全局文件中对 GET POST COOKIE 做addslashes (甚至是在带入查询的函数中再做了转义或者预编译 这种给跪) 所以基本不用担心哪里遗漏了哪里忘记了addslashes) 这种的基本是首先先get magic quotes gpc 判断gpc是否开启 如果没开启的话 再调用addslashes来转义 。 如果开启的话 就不用来addslashes了。 没开启就addslashes.

这里主要讲的就是这种类型的注入的一些常见的

宽字节注入


这个是一个老生常谈的问题, 从一开始的数据库字符集GBK的宽字节注入 到现在也有很久了。

但是并不是字符集为GBK的就能宽字节注入。

总有一些小伙伴说咋我看的cms 字符集是gbk的 但是咋不能宽字节呢?

这是因为数据库的连接方式不同

Set names gbk 这样的就能宽字节

但是现在这样的基本都看不到了。 因为基本都是设置了二进制读取了。

Binary。

这样的宽字节基本没了, 却有了另外一种。

因为转换字符集造成的宽字节注入

从utf8转到gbk 或者从gbk转到 utf8啥的。

例子: WooYun: 74cms 最新版 注入8-9

錦 从UTF8 转成 GBK之后成了 %e5%5c74cms对GET POST COOKIE …… 都做了addslashes所以' 转义后为\'->%5C %e5%5c%5c' 两个\ 则单引号出来

例子2: WooYun: qibocms 下载系统SQL注入一枚(官网可重现)

解码导致


因为在全局文件中addslashes

如果我们能找到一些解码的 例如urldecode base64_decode的

那么我们先提交encode之后的 那么就能不被转义了。

然后decode后 再带入查询 造成了注入 无视gpc。

这种的很常见。

例子很多 随便找一个

例子: WooYun: qibocms B2b 注入一枚 //qibocms 注入

例子: WooYun: phpdisk V7 sql注入2 //phpdisk 注入

变量覆盖


常见的变量覆盖 有啥extract 和 parse_str 函数啥的

当然还有$$

变量覆盖得结合一些具体的场景了。

例如extract($_POST)啥的 直接从POST数组中取出变量

这样的还是遇到过几个 然后覆盖掉之前的一些变量。

覆盖的话 一般是覆盖掉表前缀之类的

Select * from $pre_admin where xxx 像这种的就覆盖掉$pre

然后直接补全语句然后注入。

例子: WooYun: qibocms分类注入一枚可提升自己为管理

例子2: WooYun: phpmps 注入一枚

当然 $$ 也挺经常用到的 这个例子很不错。

例子3: WooYun: MetInfo最新版(5.2.4)一处SQL盲注漏洞

一些replace造成的


一些cms中 总有一些逗比过滤函数

会把’ 啥的 replace 成空

但是他似乎忘记了自己全局有转义?

用户提交一个' 全局转义成\' 然后这过滤函数又会把 ' replace 成空

那么就留下了\ 导致可以吃掉一个单引号 是double query的话

Select * from c_admin where username=’admin\’ and email=’inject#’

这样就可以注入了。

话说之前还遇到过一个厂商。。 之前提交了漏洞 是因为他会把

' " 都会替换成空 然后提交之后 他就去掉了' 就是不把' 替换成空了。

但是他似乎忘记了 " 也会被转义。。 那么提交一个 " 就又剩下了一个转义符。

例子: WooYun: PHPCMS全版本通杀SQL注入漏洞

当然还有一些replace 是用户可控的。就是说用户可以想把啥提交成空就提交成空

例如很久前的cmseasy 和 ecshop的那个注入

例如这段代码

$order_sn = str_replace($_GET['subject'],'',$_GET['out_trade_no']);

这里因为会被转义 如果提交 ' 就成 \' 这里可以看到

这里清成空的 是我们get来的 那我们就想办法把\ replace掉

但是如果我们GET提交把\ replace 那么会被转义 就是replace掉\

但是我们只是 \' 所以不能把\去掉 如果我有\ 还要你清空个毛啊。

这里我们来理清一下思路。

Addslashes 会对' " \ NULL 转义

' =>  \'
" => \"
\ => \\
NULL => \0

那这里我们就提交 %00’ 就会被转义生成 \0\' 这时候我们再提交把0替换成空 那么就成了\' 单引号也就成功出来了。

例子: WooYun: cmseasy绕过补丁SQL注入一枚

SERVER 注入


因为在很多cms中 基本上都只是对GET POST COOKIE 进行addslashes

而没有对SERVER进行转义。

而一些SERVER的变量也是用户可以控制的。

例如啥 QUERY_STRING X_FORWARDED_FOR CLIENT_IP HTTP_HOST ACCEPT_LANGUAGE 很多。

这里最常见的当然也就是X_FORWARDED_FOR

这个一般是在ip函数中用到 如果后面没有进行验证ip是否合法的话就直接return 这个大部分时候都会导致注入。

例子1: WooYun: Phpyun注入漏洞二

这里说到验证ip 这里基本都是用的正则来验证是否合法。

而一些厂商连正则都写错。

例如在cmseasy中的验证ip的正则中(%.+)

导致了后面可以写任意字符。

例子2: WooYun: CmsEasy最新版本无限制SQL注射

最近自己在看douphp 里面的验证ip的正则自己也发现了一点小问题。

不过也就只是小问题而已。

Douphp中的获取ip的函数。

function get_ip() {
static $ip;
if (isset($_SERVER)) {
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
} else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
$ip = $_SERVER["HTTP_CLIENT_IP"];
} else {
$ip = $_SERVER["REMOTE_ADDR"];
}
} else {
if (getenv("HTTP_X_FORWARDED_FOR")) {
$ip = getenv("HTTP_X_FORWARDED_FOR");
} else if (getenv("HTTP_CLIENT_IP")) {
$ip = getenv("HTTP_CLIENT_IP");
} else {
$ip = getenv("REMOTE_ADDR");
}
}

if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $ip)) {
return $ip;
} else {
return '127.0.0.1';
}
}
}

来看看验证ip是否合法的正则

preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $ip)

这里我们仔细来看看 他这里是准备匹配小数点 但是他直接写成了.

都知道在正则中.表示的是匹配任意字符 除开换行符意外 但是在开启/s 修正符以后 换行符也会匹配。

不过他这个.后面没啥+或者?的 导致也就只能写一个字符。

他这里直接写成了. 那在这里我们就能引入单引号了。不过也就一个字符。

这里的正确写法应该是.

FILES注入。


也差不多 也是因为全局只对COOKIE GET POST 转义 遗漏了FILES 且不受gpc。

FILES 注入一般是因为上传 会把上传的名字带到insert当中入库。

然后这里文件的名字是我们可以控制的 所以导致了注入。

而这里的上传的名字是我们可以控制的。

例子: WooYun: qibocms 黄页系统SQL注入一枚

还有一些 在入库的时候才对文件的名字进行了转义 而在获取后缀后 在入库的时候对文件名转义了却没有对后缀转义也导致了注入

例子: WooYun: Supesite 前台注入 #2 (Insert)

未初始化造成的注入


很久以前php<4.20的时候 为了方便 register_globals 默认都是on。

而到了后面 register_globals 的弊端也显现了出来, 所以也在很久以前默认都是off了。

而到了现在, 很多cms 却喜欢模仿register_globals 搞起了伪全局机制。

例如啥qibocms metinfo destoon 啥的啊。

这样是方便了不少, 但是如果哪里遗漏了初始化 那么就会导致注入了。

感觉这种的挺好玩的 多找了几个例子。

例子: WooYun: qibocms地方门户系统注入一个问题(demo测试)

例子: WooYun: qibocms地方门户系统注入(多处类似,demo测试)

例子: WooYun: 齐博地方门户系统SQL注入漏洞(无需登录可批量)

例子: WooYun: 齐博整站/地方门户SQL注入漏洞

数组中的key。


因为在对全局转义的时候

很多cms 都只是判断gpc 是否开启

如果off 就对数组中的value就行addslashes

却忘记了对数组中的key进行转义。

那么这样也导致了一个问题。 也就是在Gpc off的时候那么数组的key没有被过滤 导致可以引入单引号。(听说低版本的php对二维数组中的key就算gpc on 也不会转义)

如果哪里把数组中的key 读取出来 然后把key带入到了查询当中

那么也会造成安全问题。

而且这样的例子很多。 简直惨不忍睹。 例子: WooYun: qibocms V7 整站系统最新版SQL注入一枚 & 另外一处能引入转义符的地方。 //数组key的注入例子: WooYun: qibocms多个系统绕过补丁继续注入2

例子: WooYun: qibocms全部开源系统 Getshell

例子: WooYun: Discuz 5.x 6.x 7.x 前台SQL注入漏洞一枚

offset


这种算是比较常见的一种注入的。

代码大概如

<?php
$key=0;
$a=$_GET[a][$key];
$b=$_GET[b];
Mysql_query("select * from table where xxx='$a' and xx='$b'")

如果这里$_GET[a] 提交的是一个数组 且含有一个key为0的那么$a就是对应的这个key的value

但是这里并没有强制要求为数组。

那么我们提交一个字符串 那么后面的[0] 那么就是截取的第一个字符

在全局中 单引号被转义为\' 截取第一个字符就为了\

吃掉一个单引号 然后就在$b处写入inject可以注入了。

例子: WooYun: qibocms 地方门户系统 注入#4(demo测试)

还有map发的那Disucz 7.2的那注入也一样。

第三方插件


很常见的一种洞。

比较常见的uc 和 alipay tenpay chinabank 啥的

特别是uc 因为默认uc里面都会striplashes

Uc的话 一般会遇到的问题是uckey默认的。

或者是uckey这个常量根本就没有初始化。

导致了uckey可控 再导致了Getshell 或者 注入啥的。

还有tenpay 和 alipay 啥的 一些是因为忘记把过滤的文件包含进来

且key默认是空的 导致可以通过验证。

例子: WooYun: phpmps 注入 (可修改其他用户密码,官网成功) // phpmps uc致注入

例子: WooYun: PHPEMS (在线考试系统) 设计缺陷 Getshell一枚(官网已shell) /phpems uc致getshell

例子: WooYun: 最土团购注入一枚可直接提升自己为管理 & 无限刷钱。 //最土团购 chinabank致注入

例子: WooYun: Destoon Sql注入漏洞2(有条件) //destoon tenpay致注入

例子: WooYun: CSDJCMS程式舞曲最新版Sql 一枚 //csdj tenpay致注入

数字型注入


其实也不只是数字型 只是说一些忘记加单引号的地方都这样。

只是一般数字型的都不会加单引号的。

一般的是

$id=$_GET[id];
Select * from table where id=$id;

$id 没被单引号 且 没有被强制类型转换 那么就算addslashes了 由于不需要去闭合单引号 所以也无影响。

例子: WooYun: qibocms 地方门户系统 注入#3 (demo测试)

并不是一些数字型 一些其他的点也有些忘记加单引号 导致了注入。 例子: WooYun: Supesite 前台注入 #3 (Delete)

这里supesite的注入还涉及到了一个设计缺陷。这里把

$query = $_SGLOBAL['db']->query('SELECT * FROM '.tname('spacetags').' WHERE itemid=\''.$itemid.'\' AND status=\''.$status.'\'')

$itemid 首先带入到了查询当中 是被单引号了的。。 如果查询出来的有结果 才会带入到delete中 如果无结果 就不执行delete的语句了。而在数据库中itemid中 存储的是int类型 所以他这里本意是想要用户只能提交数字型才能查询出结果。 如果不是提交的数字的话 那么就查询不出来结果 就不去执行下面的delete语句了。但是由于mysql的类型转换 因为他这里储存的是int类型 所以我们提交4xxxxx 跟我们提交4 是一样的

$_SGLOBAL['db']->query('DELETE FROM '.tname('spacetags').' WHERE itemid='.$itemid.' AND tagid IN ('.simplode($deletetagidarr).') AND status=\''.$status.'\'');

然后就执行这个delete语句 然后没单引号 造成了注入。

例子: WooYun: phpyun v3.2 (20141226) 两处注入。

这个phpyun的注入 主要是因为php是弱类型语言

一些厂商喜欢这样写

If ($a>1){
Mysql_query(select id from table where id=$a)
}

他这个本来是想用户提交数字才能通过这个判断 但是由于弱语言 1+asd啥的 都能通过 所以又导致了注入。

二次注入


也是一种比较常见的注入。 涉及到的是入库和出库。 因为有全局转义 然后入库的时候

Insert into table (username) values ('a\'');

这样入库后 转义符就会消失 那么就是a' 如果哪里再把这个查询出来 那么也就是出库的是a' 如果再把出库的 再带入到了查询啥的 那么就再次成功的引入了单引号导致了注入

例子: WooYun: phpyun v3.2 (20141226) 两处注入。 例子: WooYun: qibocms 地方门户系统 二次注入#5(demo测试) 例子: WooYun: 74cms (20140709) 二枚二次注入 例子: WooYun: Hdwiki最新版二次注入一枚

比较是硬伤的是 很多时候数据库中存储的长度是有限制的。 所以一些也不是太好利用。

查询当中key可控


不知道也应不应该把这个归为一类。

大概是因为一些查询的时候 直接把$_POST啥的 直接带入到了查询函数当中

例如cmseasy的rec_insert的查询函数中。

然后foreach key 出来 然后foreach 出来的key 做了查询中的column

这种的防止方法一般是 把数据库中的column查询出来 然后in_array 判断一下$_POST出来的key 是否在数据库中的column中 下面两个例子就是这样修复的。

例子: WooYun: 云人才系统SQL注入,绕过WAF 例子: WooYun: Cmseasy SQL注射漏洞之三

striplashes


有些cms 在全局addslashes后 然后在后面的文件中又stripslashes 去掉了转义符 然后又可以闭合单引号了。

$_SESSION['flow_consignee'] = stripslashes_deep($consignee);

例子: http://www.2cto.com/Article/201301/182509.html //之前的ecshop注入 。

截取字符导致的注入


有些cms 有的时候会限制用户输入的长度

所以只截取一部分

例如uchome的cutstr($asd,32);

这样只允许输入32个字符 而且uchome里面的这个也没有像dz那样截取字符的后面加...

那么如果我们提交一个1111111111111111111111111111111’

被转义后成1111111111111111111111111111111\’

然后截取32个字符 就是1111111111111111111111111111111\

如果又是double query的话 吃掉一个单引号 然后下一个连着的可控变量又可以注入了。

结果在uchome中找到了个能引入转义符的 结果只有一个可控的。

例子: WooYun: Hdwiki (20141205) 存在7处SQL注入漏洞(含之前处理不当安全的漏洞) //里面的0x06

绕过限制继续注册GLOBALS变量


不知道放哪。这个也放到注入板块来把。。

其实就是这次的DZ6.X 7.X 那个任意代码执行的漏洞

if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
exit('Request tainting attempted.');
}

foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value);
}
}

主要关键代码就上面这两段。 这里把GET POST COOKIE 循环出来 然后注册一个变量 但是 这里不允许创建GLOBALS变量 然后DZ7.X 就是用这样处理的 如果设置了REQUEST 的 GLOBALS

就直接退出

这段代码在很久以前确实是没什么问题

因为那时候的request order 还是gpc

但是在php 5.3 以后 request order 默认成了gp

也就是成了get 和 Post 不包含cookie了。

所以 $_REQUEST里面就不包含COOKIE提交来的了。

而且这后面也把COOKIE循环出来 注册变量

所以这里我们在COOKIE里面提交GLOBALS 就不会被检测出来了。

而且也成功注册了GLOBALS变量。

所以在结合后面的一些些代码就造成了代码执行。

例子: WooYun: Discuz!某两个版本前台产品命令执行(无需登录)

以上就差不多是我经常所遇到的注入问题 好像暂时也想不到其他什么的了