foreach()

手册
用于遍历数组,一共有两种格式

foreach (iterable_expression as $value)
    statement
foreach (iterable_expression as $key => $value)
    statement

第二种会把当前单元的键名也赋给变量$key
foreach可以通过在$value之前加上 & 来修改数组的元素。此方法将以引用赋值而不是拷贝一个值

<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
    $value = $value * 2;
}
// 现在 $arr 是 array(2, 4, 6, 8)
unset($value); // 最后取消掉引用
?>

数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用 unset() 来将其销毁

preg_match_all

手册

preg_match_all(
    string $pattern,//要搜索的模式,字符串形式
    string $subject,//输入字符串
    array &$matches = null,//多维数组,作为输出参数输出所有匹配结果, 数组排序通过flags指定
    int $flags = 0,//见手册
    int $offset = 0//用于 从目标字符串中指定位置开始搜索(单位是字节)
): int|false

搜索subject中所有匹配pattern给定正则表达式 的匹配结果并且将它们以flag指定顺序输出到matches
在第一个匹配找到后, 子序列继续从最后一次匹配位置搜索
正则表达式网站

in_array

手册

in_array(mixed $needle, array $haystack, bool $strict = false): bool

大海捞针,在大海(haystack)中搜索针( needle),如果没有设置 strict 则使用宽松的比较。

绕过

在宽松比较中,字符串开头为数字可以将整个比较变为数字的比较从而导致绕过

call_user_func_array

手册

调用回调函数,并把一个数组参数作为回调函数的参数

call_user_func_array(callable $callback, array $args): mixed

把第一个参数作为回调函数(callback)调用,把参数数组作(args)为回调函数的的参数传入
callback为函数名,而array $args则是函数参数

<?php
function foobar($arg, $arg2) {
    echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
    function bar($arg, $arg2) {
        echo __METHOD__, " got $arg and $arg2\n";
    }
}


// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));

// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>

输出:

foobar got one and two
foo::bar got three and four

exec

手册

执行一个外部程序

exec(string $command, array &$output = null, int &$result_code = null): string|false

exec() 执行 command参数所指定的命令

result_code 外部命令执行后的返回状态将会被设置到此变量中

类似于system()

system

执行外部程序,并且显示输出

system(string $command, int &$result_code = null): string|false

执行 command 参数所指定的命令,并且输出执行结果
result_code 外部命令执行后的返回状态将会被设置到此变量中
成功则返回命令输出的最后一行,失败则返回 false

strcmp

手册

strcmp(string $string1, string $string2): int

该比较区分大小写

如果 string1 小于 string2 返回 -1;如果 string1 大于 string2 返回 1;如果两者相等,返回 0

挨个比较ascii码

绕过

strcmp("foo", array()) => NULL + PHP Warning
strcmp("foo", new stdClass) => NULL + PHP Warning
strcmp(function(){}, "") => NULL + PHP Warning

strcmp()函数传入数组会返回NULL 在php 中 NULL==0

eregi or ereg

手册

eregi不区分大小写的正则表达式匹配

eregi ( string $pattern , string $string [, array &$regs ] ) : int

本函数和 ereg() 完全相同,只除了在匹配字母字符时忽略大小写的区别

以(不)区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串

如果找到与 pattern 中圆括号内的子模式相匹配的子串并且函数调用给出了第三个参数 regs,则匹配项将被存入 regs 数组中。$regs[1] 包含第一个左圆括号开始的子串,$regs[2] 包含第二个子串,以此类推。$regs[0] 包含整个匹配的字符串

如果在 string 中找到 pattern 模式的匹配则返回 所匹配字符串的长度,如果没有找到匹配或出错则返回 FALSE。如果没有传递入可选参数 regs 或者所匹配的字符串长度为 0,则本函数返回 1

绕过

两者存在%00截断类似于\0截断

preg_match()

php

preg_match(
    string $pattern,
    string $subject,
    array &$matches = null,
    int $flags = 0,
    int $offset = 0
): int|false

搜索subjectpattern给定的正则表达式的一个匹配
matches
如果提供了参数matches,它将被填充为搜索结果。 $matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推

无字母数字webshell

参考

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
  eval($_GET['shell']);
}

对于类似与以上正则匹配进行绕过

PHP中的短标签

<??>相当于对<?php>的替换。而<?=?>则是相当于<? echo>

<?= '111'?>

PHP中,反引号可以起到命令执行的效果

<?php
$_=`whoami`;
echo $_;

如果我们利用上面短标签的写法,可以把代码简写

<?= `whoami`?>

临时文件泄露

vim 临时文件泄露

http://node4.anna.nssctf.cn:28196/.index.swp

得到之后使用vim即可恢复

vim -r .index.swp

preg_replace与代码执行

preg_replace(
    string|array $pattern,
    string|array $replacement,
    string|array $subject,
    int $limit = -1,
    int &$count = null
): string|array|null

搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。

replacement 中可以包含后向引用\\n$n,语法上首选后者。 每个这样的引用将被匹配到的第 n个捕获子组捕获到的文本替换。 n可以是0-99\\0$0 代表完整的模式匹配文本。捕获子组的序号计数方式为:代表捕获子组的左括号从左到右, 从1开始数。如果要在 replacement 中使用反斜线,必须使用 4 个(“\\\\”,译注:因为这首先是 PHP 的字符串,经过转义后,是两个,再经过正则表达式引擎后才被认为是一个原文反斜线)。

参考

preg_replace /e 模式下的代码执行问题

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


foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行

而这里面第二个参数含有\1那么他会将被匹配到的第 1 个捕获子组捕获到的文本替换,总之就是\1也被替换了,导致的命令执行
又因为特殊字符.会被替换成_所以我们这里传参是\S*=别问为啥是\S我不到啊
payload : \S*=${phpinfo()}

什么要匹配到{${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。

escapeshellarg()和escapeshellcmd()同时使用导致的绕过

escapeshellarg() 将给字符串增加一个单引号并且能引用或者转义任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。

反斜线(\)会在以下字符之前插入:&#;`|*?~<>^()[]{}$\、\x0A 和 \xFF。 ‘ 和 “ 仅在不配对儿的时候被转义。在 Windows 平台上,所有这些字符以及 % 和 ! 字符前面都有一个插入符号(^)。

它们组合使用时就会造成漏洞(顺序不能改变),就借用一个大佬的例子,通俗易懂。

传入的参数是:172.17.0.2' -v -d a=1
经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php
最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。

也就是说,escapeshellcmd()函数转义了用于转义单引号的斜杠,导致这个单引号与后面的单引号形成了空白连接符就能执行命令了。

nmap构造命令

system函数里拼接了nmap的指令字符串。nmap中的-oG参数可以将代码与命令写到文件中,比如nmap <?php phpinfo();?> -oG 1.php,就是将这个phpinfo();语句写在了1.php里内了。


一个好奇的人