分类 coding 下的文章

某电商CMS前台代码注入

最近日站的时候遇到了某个cms,于是花时间审计了一发,收获颇丰,这里先放一个比较严重的问题出来。
(本文同步发表在先知社区——https://xianzhi.aliyun.com/forum/topic/1982

审计入口

全局变量$_GET、$_POST、$_COOKIE作了转义处理。一般全局作了转义以后程序员在后面写逻辑的时候就会比较放心大胆地进行各种拼接了。因此我们在审计代码的时候可以着重关注$_SERVER、$_FILE、$_REQUEST等变量。

IMG20180123_112451.png

Lang::get() 用于获取指定键的语言项。举个例子,传入'i_want_open_store',返回'我要开店'。
IMG20180123_123639.png

$vkey是个由$key经过一些处理得到的字符串,字符串的内容是$GLOBALS数组变量,需要用eval函数来使它生效。

IMG20180123_112855.png

在eval之前会调用strtokey()方法,作用就是传入$key, $owner, 返回字符串"$owner['$key的值']"。如果这里的$key包含单引号且没被转义呢?比如这里传入 $key="'xor(phpinfo())or'" 生成的$vkey就是字符串"$GLOBALS['ECLANG'][''xor(phpinfo())or'']",这个字符串会最终进入eval造成代码注入。

IMG20180123_122013.png

也就是说,如果传入Lang::get()的参数包含没被转义的单引号就存在问题,而这个Lang::get()应用是相当之广的。。。

举个栗子。

漏洞复现

进入用户中心-个人资料,随意选择一张图片上传,保存修改。
http://localhost/cms/xxx/index.php?app=member&act=profile

IMG20180123_103502.png

修改filename字段的值为 1.png'xor(phpinfo())or',成功执行phpinfo()。

IMG20180123_103701.png

漏洞分析

根据上述poc定位到member.app.php profile()方法313行,上传资料时如果存在图片文件则调用_upload_portrait()对图片进行上传处理

IMG20180123_104932.png

跟进到_upload_portrait()中。ecmall所有的图片上传都由Uploader这个图片上传辅助类来完成,这里也不例外。首先设置了allowed_type为图片类型,然后调用addFile()方法进行上传处理。

IMG20180123_105537.png

跟进addFile()

IMG20180123_105943.png

跟进_get_uploaded_info()。可以看到由于上传的后缀png'xor(phpinfo())or'没有在allowed_type里面,这里进行了错误存储。错误消息'not_allowed_type'和错误的后缀png'xor(phpinfo())or'被存储到了Uploader类的_errors[]数组里面,键名分别为msg和obj。注意,这里因为后缀是从$_FILE里获取的,不会受到全局转义的影响。

IMG20180123_110137.png

IMG20180123_110539.png

跳出addFile()以后,由于上传出错,调用show_warning()进行错误处理和错误警告。

IMG20180123_111048.png

IMG20180123_111247.png

IMG20180123_111324.png

可以看到在_trigger_message()方法中多处调用了Lang::get(),且其中一处$err['obj']被作为了参数,本文最初时就提到

如果传入Lang::get()的参数包含没被转义的单引号就存在问题

而$err['obj']恰恰是错误的后缀名png'xor(phpinfo())or',最终导致了php代码注入。

IMG20180123_111837.png

修复建议

在strtokey()函数对传入的$str进行转义处理。参考代码

function strtokey($str, $owner = '')
{
    $str = addslashes($str);
    if (!$str)
    {
        return '';
    }
    if ($owner)
    {
        return $owner . '[\'' . str_replace('.', '\'][\'', $str) . '\']';
    }
    else
    {
        $parts = explode('.', $str);
        $owner = '$' . $parts[0];
        unset($parts[0]);
        return strtokey(implode('.', $parts), $owner);
    }
}

Struts2-016 动态调试与分析

最近终于下定决心开始研究一下java web的安全。相比起php web, 调试java web往往会出现各种各样的环境问题,其中一个原因就是开发java web应用会大量引用已存在的各种成熟组件,如同建筑一栋高楼,如果其中一根底层支柱出了问题,整栋大楼就构建不起来。大部分想要去做安全研究的同学其实都没有太多的开发经验,因此在环境调试上面的问题会变得非常棘手。

这就成为了不少想要入门java web安全的同学的一道高门槛,比如你兴致勃勃地拿到了一个java web应用项目代码,开始构建应用的时候发现爆了一个错误,解决这个错误可能就会耽搁你一下午,等到你解决完了这个问题,你最初的激情可能已经磨灭了一大半了。其实如果最终能够解决问题也还算不错,更糟的情况可能是你至始至终都没有解决这个问题。

以上其实就是我个人在这段时间研究java web时的一些感想。废话不多说了,下面我会以一个java web安全初学者的视角,比较详细地记录自己调试struts2-016的过程。

0x01 环境搭建

Struts是apache基金会jakarta项目组的一个开源项目,采用MVC模式,能够很好的帮助我们提高开发web项目的效率。Struts主要采用了servlet和jsp技术来实现,把servlet、jsp、标签库等技术整合到整个框架中。

我下载的struts2源码版本是 struts-2.3.14.3-src.zip(不带jar包),目录结构如下。
E1B40911-87AD-4A05-85E5-F70834958AC6.png

我常用的ide是jetbrain idea,在示例web应用中选择了代码最简洁的struts-blank应用。首先open project,选择apps/blank文件夹,然后等待maven下载完依赖的jar包。
依次打开project structure ,选择library,如下图所示关联struts2-core 和 xwork-core两个包的源码,源码分别在core目录和xwork-core目录。
14B97AB1-CC0C-40B5-A2B2-F5DF175EF7BC.png

然后配置下运行环境,这个就不多介绍了。最终成功运行如下。
FD0A85EA-EA25-45B3-93EF-702A656154B3.png

用以下poc打下

redirect:$%7b%23w%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),%23w.println('struts2-016'),%23w.flush(),%23w.close()%7d

1E87F568-061C-4940-A87C-1F3BDE48994B.png

0x02 动态调试

StrutsPrepareAndExecuteFilter 是struts2框架的默认filter,所有的filter都需要在web.xml中进行配置。
D6419166-1F8E-4A11-AA7C-A182F87BDC29.png

StrutsPrepareAndExecuteFilter 作用是过滤所有的action请求并进行映射,简单的理解就是路由分发。比如用户请求HelloWorld.action时,就会根据相应的路由规则映射到controller层的HelloWorld类,经过处理后再渲染到view层/example/HelloWorld.jsp。相应的映射规则在struts.xml中配置。

struts.xml
EA15D4BF-5649-48A4-9A0C-9EB67261ADAC.png

example.xml
9026EE95-BDC7-4256-AFB3-44338B81D5AF.png

简单的了解了StrutsPrepareAndExecuteFilter的作用后,我们开始在第77行下一个断点进行调试。
BCED8879-5026-49B1-B8D5-BB8B6F331323.png

上图中是doFilter的代码,理解这个方法其实就是理解整个struts2框架路由映射的关键。像struts2这样知名的开源框架,其命名必然是有道理的,有的时候我们可以根据方法名来直接判断这个方法的大致作用(这样可以略过一些于漏洞本身无关紧要的方法)。
比如上图77行的setEncodingAndLocale(),字面意思就是设置编码和场景,其作用就是设置编码和初始化环境。
78行的createActionContext(),即创建action的上下文。ActionContext正如其名,是Action执行的上下文,它包含了request、session、application、action、attr、parameters等属性。
83行这里先略过。17年三月份爆发的struts2 045漏洞就与它有关。
84行findActionMapping()。寻找action的映射,按f7跟进函数中。
EB1FE903-E855-439B-A775-0E1EBC2BDE48.png

再跟进getMapping(),可以看到一个handleSpecialParameters()的调用。函数名的字面意思为“处理特殊的参数”,而特殊的参数就是我们poc里面的redirect:${xxxxx}
B1244EA6-ECDE-4116-ABD1-2819CEB36A18.png

f7跟进handleSpecialParameters()。首先提取出了key redirect:${xxxxx},然后把key作为参数之一调用了parameterAction.execute()。
4EB4E5B8-844B-4C47-A9B1-48913717FEF0.png

继续f7,进入了第一个箭头所指的代码段。提取出了ognl表达式${xxxxx}并赋值给了redirect的location属性。
66C3372E-58A5-43A4-A5F7-0A151A436D06.png

至此。我们已经分析完了 struts2 中特殊参数redirect的作用。如http://localhost:8081/example/HelloWorld.action?redirect:http://www.baidu.com就会把http://www.baidu.com赋值为redirect的location属性并302跳转到百度。

那么什么时候最终执行了ognl表达式呢?我们跳出到最开始的doFilter()里面。。
E0D46F01-209F-4E12-87C1-C48FBD5B0AAE.png

跟进91行,executeAction()。这里没什么好说的。
63D348DD-CC4C-48C2-8876-E67CCE99C58D.png

继续f7,进入了Dispatcher类的serviceAction()。前面已经完成了action的映射(正常情况下是HelloWorld.action->HelloWorld.java,目前是要处理redirect),serviceAction()的544行的作用就是执行映射后的逻辑代码(正常情况下执行HelloWorld.java里的具体逻辑代码,不过这里是执行redirect)。
799DC750-B201-466A-80C9-80B8E22E8984.png

跟进execute()。后面的逻辑都比较简单,按顺序一路跟进。
FB05878E-7E50-4B3E-89EC-73E9B44A1285.png
E4C9B23E-1FEA-4A76-B091-CDDCB175A15A.png

conditionalParse()。包含了ognl表达式的location被作为了conditionalParse()的参数,并最终在translateVariables()中执行了ongl表达式。
4A46FBB8-92C7-449C-AE91-6A32C332C85C.png
87BBEA98-F453-4EA0-A5B9-A5C92A9E4176.png
14473BF2-ACCA-47D8-AFC9-274EE8F301C8.png

0x03 思考

为什么会在conditionalParse()里面解析ognl?开发者是出于一个怎样的意图?

参考资料

攻击JavaWeb应用[5]-MVC安全

填坑系列之xss 蠕虫

最近这个月庆总给我安排的任务是前端安全的学习,系统地看了一遍《web前端黑客技术揭秘》这本书,填了不少坑(这里特别要讲一下,看博客、看帖子确实能学到很多奇淫技巧,但如果要系统地学习一个知识点,个人建议还是看书比较好,书会全方面无死角地介绍)。其中xss蠕虫这块,之前一直是抵触的,现在发现读起来其实并不困难。本文从产生xss蠕虫的条件和特性讲起,主要以08年百度空间xss蠕虫为例(sammy写的那段我是拒绝的),深入体会其魅力所在。难免有描述错误,欢迎指正。

蠕虫源码:Baidu xss蠕虫
整理过的蠕虫源码:xss_worm.js(utf-8格式保存的)

- 阅读剩余部分 -

代码审计中else if的执行逻辑

0x00 来源

水一篇文,来源于一道ctf题,跟if,else if的执行逻辑有关。先看部分题目代码(检测sql注入关键字):

if (!empty($_GET)){
        foreach($_GET as $value){
            if (preg_match('/(and|or|union|select|update|delete|insert|fro
m|where|limit|sleep|count|concat|rand|floor|substr|ascii|char|mid|order|ve
rsion\(\)|database\(\)|user\(\)|\#|\-\-|\'|\"|\=|\*|\&|\|\`)/i', $value)){
                die('Bad request!');
            }
        }
    } else if (!empty($_POST)){
        foreach($_POST as $value){
            if (preg_match('/(and.*\=.*|or.*\=.*|\&\&.*\=.*|\|\|.*\=.*|uni
on select|select.*from.*where|limit .*,.*|sleep(.*)|if(.*)|order by [0-9]*
|left(.*,.*)|\`)/i', $value)){
                die('Bad request!');
            }
} }
    foreach($_REQUEST as $key => $value){
        if (!is_array($value)){
            $_REQUEST[$key] = addslashes($value);
        }
    }
    extract($_REQUEST);

乍看没看出啥明显能轻松绕过的手段呀。

0x01 else if的执行逻辑

百度了一下else if的执行逻辑后我好像发现了什么。

int i = 4;
if (i > 0)
...
else if (i > 1)
...
else if (i > 2)
...
只会执行到 i > 0 那段

当多个分支都满足条件的时候,如果用的是elseif,将只会执行最先满足的那个分支。那么绕过的答案就呼之欲出了,比如a是注入点,后端使用$_REQUEST['a']的方式接收,那么就可以在get请求的同时post提交payload,就可以绕过上述代码中sql注入的关键字检测了。
2.png

很容易忽略的一个小问题,其实仔细想想很好理解,elseif就是先else再if嘛。