专栏首页湛卢工作室CTF杂谈之PHP魔法与CBC加密

CTF杂谈之PHP魔法与CBC加密

PART ONE ---- PHP黑魔法

PHP语言的开发者在几乎所有内置函数以及基本结构中使用了很多松散的比较和转换,防止程序中的变量因为程序员的不规范而频繁的报错,然而这却带来了安全问题。也正是因为这些PHP特性,使得它频繁出现在各类CTF题目中。 在开始今天的重点之前,我们先复习一下以前遇到过的一些PHP黑魔法。

1.要求变量不相等,但变量的md5值相等 ==是比较运算,它不会去检查条件式的表达式的类型,===是恒等,它会检查查表达式的值与类型是否相等 a) 0e的数都相等(==) 240610708、QNKCDZO这两个字符串,经过md5运算后,都为0e的形式,满足弱相等的条件 b) 数组的md5都相等(===) http://127.0.0.1/CTF/index.php?username[]=1&password[]=2 username 与 password 是两个不同的数组,但数组经md5运算都得到null的空值,满足强相等的条件。

2.Strcmp()利用数组绕过 int strcmp ( string $str1 , string $str2 ) Strcmp()函数用于比较两个字符串,如果str1和str2相同,则返回值为0;如果不同则返回值为正或者为负。但PHP内置函数不太限制传入参数的类型,所以当输入的值不是字符串时,就会产生不预期的返回值。 例如,我们传入一个数组,就会返回NULL,绕过判断。

3.Ereg()函数匹配 ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。 a) 数组绕过 与strcmp()类似,传入参数类型是数组的时候,返回NULL b) %00截断绕过 例题中对输入的password又多重限定:

ereg("^[a-zA-Z0-9]+$", $_GET['password']) !=== FALSE

password只能由一个或多个字母和数字组成

(strlen($_GET['password'])< 8 && $_GET['password'] > 9999999

password的长度小于8,但数值要大于9999999

strpos($_GET['password'], '*-*') !== FALSE

password中要包含这个星星眼的表情符号

这些条件互相矛盾,可以说是脑洞很大了。但是在PHP灵感的黑魔法面前,这一切都可以实现。。。

我们用%00截断绕过正则匹配;用科学计数法绕过数值限定,构造

Password=1e8%00*-*

PHP的特性其实非常多,比如有名的PHP伪协议就可以作为一个专题来讲。还有一些数值计算、PHP彩蛋等trick,在我们做题的过程中会越来越多接触到。接下来,我们来看今天的第一个重点题型。

例题一 http://45.76.173.177:23334

<?php

$MY = create_function("","die(`catflag.php`);");

$hash = bin2hex(openssl_random_pseudo_bytes(32));

eval("function SUCTF_$hash(){"

."global\$MY;"

."\$MY();"

."}");

if(isset($_GET['func_name'])){

$_GET["func_name"]();

die();

}

show_source(__FILE__);

首先,分析PHP代码:

Openssl_random_pseudo_bytes()函数的作用是生成指定字节长度的随机数;Eval()函数的作用是把字符串当做PHP语句执行。那么,这段代码中,通过GET请求得到Func_name参数,执行同名函数,我们知道SUCTF_$hash()函数中通过MY变量cat了flag的值。那么如果能知道$hash的值,就能指定Func_name= SUCTF_$hash(),得到flag。

但是这个随机数之所以随机,就是因为我们是猜不到的。它放在这里只是个幌子。第一行代码中,隐藏着本道题的第一个考点:匿名函数。

匿名函数,也叫闭包函数,允许指定一个没有名称的函数。把匿名函数赋值给变量,通过变量来调用。

匿名函数其实是有名字的,查看源代码可以看到大佬还专门写了个注释调侃它:

匿名函数的命名规则是:\0lambda_%d。其中\0 是空字符,%d 是当前匿名函数的个数+1。这里就出现了另一个问题,我们并不知道当前情况下,匿名函数有多少个,这就引出本题的另一个考点:

Apache工作模式

Apache工作模式有prefork worker event 三种,他们的区别主要在于进程和线程的处理方式。默认的模式是prefork

在prefork工作模式下,默认生成5个子进程,默认最多能够生成256个子进程。针对每个子进程,参数MaxConnectionsPerChild 限定了每个子进程在其生命周期内允许最大的请求数量,如果请求总数已经达到这个数值,子进程将会结束。

考虑通过大量的请求来迫使Pre-fork模式启动的Apache启动新的线程,这样这里的%d会刷新为1。这里给出Orange大牛给的fork脚本

fork后访问链接

http://45.76.173.177:23334/?func_name=%00lambda_1

就能拿到flag。当然也可以直接爆破。这道题其实是根据hitcon2017的一道题改编的,那道题很难,据说在比赛过程中是0解。这里给出一个链接,大家可以自行研究。

https://www.jianshu.com/p/19e3ee990cb7

PART 2 ----CIPHER BLOCK CHAINING

之前的交流我们讲到了对称加密和非对称加密,讲到了非对称加密中有公钥和私钥。对称加密运算速度快,非对称加密由于需要寻找大素数、大数计算、数据分割等需要耗费许多CPU周期,所以性能比较低。

在现实应用,例如HTTPS连接中,只在第一次握手时使用非对称加密,通过握手交换对称加密密钥,之后的通信用对称加密完成:服务端向客户端发送证书/公钥,客户端验证证书的有效性后,生成一个随机值,用该证书加密。这个随机值就是通信时的密钥。

对称加密可以分为流加密 Stream Cipher和块加密 Block Cipher。流加密一般逐字节或者逐比特处理信息,块加密则顾名思义,对明文分块后进行加密,也叫分组加密。

香农提出设计密码体制的两种基本方法:

扩散(diffusion):让明文中的每一位影响密文中的许多位,或者说让密文中的每一位受明文中的许多位的影响,这样可以隐蔽明文的统计特性。常见的方法有循环移位、置换等;

混淆(confusion):将密文与密钥之间的统计关系变得尽可能复杂,使得对手即使获取了关于密文的一些统计特性,也无法推测密钥。一般使用复杂的非线性变换可以得到良好的混淆效果。

扩散和混淆是现代分组密码的设计基础。

块加密有多种方式,这里介绍比较常见的四种:

1. ECB(Electronic Code Book)

优点:并行;块与块之间没有错误传播

缺点:无法隐藏明文模式

2. CFB(Cipher Feedback)

实际上,IV是一个大小为n的移位寄存器S,对于一个明文分组,加密时通过异或一个将移位寄存器加密得到的密文的 r 比特,从而得到密文分组。这个密文分组将填充 移位寄存器左移 r 比特后 最右边的 r 比特,得到新的移位寄存器进行之后的加密。

3. OFB(Output Feedback)

OFB与CFB非常相似,区别在于CFB将密文作为下一次算法的输入;而OFB将第一次的算法输出作为第二次算法的输入。

优点:不具有错误传播特性

4. CBC(Cipher Block Chaining)

明文块加密前同上一个密文块做异或,加密不同的消息使用不同的IV

优点:明文的微小变动会影响所有的密文

缺点:串行加密;一个密文分组的错误会导致两个密文分组无法正确解密

CBC字节翻转攻击

所谓CBC字节翻转攻击,原理是:

在CBC模式下的解密过程中,前一块密文会参与后一块密文的解密。

对于密文块中的某一位A,有 C=A^B,所以 A^B^C=0

现在如果让A 变成 A^C 那么原来的C就会变成0

所以,如果我们要控制C这个位置的值,只需要另 A 变成 A^C^X,那么C就会变成 X

例题二

http://123.206.31.85:49168/index.php

Bugku Login4

1. 尝试扫描是否有敏感文件泄露

发现.index.php.swp文件,这是index.php文件异常退出时系统自动的备份文件,可以恢复源代码;

vim-r index.php.swp

:w./index.php

参考:WEB题附加信息获取方法

a) 查看页面源代码;

b) 抓包查看数据包中是否有提示信息;

c) 搜索未知页面、敏感文件等,可以借助一些扫描工具

.bak .backup .svn .index.php.swpRobots.txt 等等

2. 查看源代码

只有admin用户可以读取flag,但是admin用户又不允许登录。

服务器将我们传入的数据构成一个数组,序列化后,用SECRET_KEY 和 iv对其做CBC加密,得到密文cipher,然后对iv和cipher做base64编码,添加到cookie中,作为当前用户的身份标识。

当我们再次发起请求时,如果不提交新的数据,服务器就会从cookie中获得这个数据,做base64解密和CBC解密,得到字符串,反序列化后得到用户名,完成身份认证。

那么,我们的思路就是先用别的用户登录,更改cookie值,让它解密后的用户名是admin。为了便于操作,我们可以使用admik登录,这样只需要更改一个字符。

当username为admik,password为123456时,info序列化得到:

a:2:{s:8:"username";s:5:"admik";s:8:"password";s:6:"123456";}

按16位分组,想要改变的值在第二组的第13位(从0开始):

a:2:{s:8:"userna

me";s:5:"admik";

s:8:"password";s:

6:"123456";}

所以我们要更改的是第一组的第13位

$cipher[13] = chr(ord($cipher[13])^ ord("K") ^ ord ("n"));

但是更改了第一组的密文后,第一组解密后的明文会变化,无法正常反序列化。因此返回包中出现报错信息。

为了解密后能得到正确的明文,我们构造一组新的iv:

$newiv[i] = chr(ord($iv[i])^ ord($enc[i]) ^ ord ($cleartext[i]));

附本题代码如下

<?php

$enc=base64_decode("bIpgPK29vVQosJ+smzh0pOdq7QrP3H9CN0MBfynL1eKtILs/ayew1snTYbeYSIz8rQctkAUMORS76SWQHXwuKg==");

$enc[13] = chr(ord($enc[13]) ^ ord("k") ^ ord("n"));

echo base64_encode($enc);

?>

<?php

$enc=base64_decode("4quudO++PAeVPQfcFJ0bbm1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ==");

$iv=base64_decode("TrphJjWLH37sj6+EBqh28A==");

$cleartext = 'a:2:{s:8:"userna';

$newiv = '';

for ($i=0;$i<16;$i++){

$newiv=$newiv.chr(ord($iv[$i]) ^ ord($enc[$i]) ^ ord ($cleartext[$i]));

}

echo base64_encode($newiv);

?>

块加密是对固定长度的数据块进行的加密,上面介绍的四种模式中,OFB、CFB都不需要对消息进行填充,因为他们经过了异或的步骤。而CBC模式是需要对明文的最后一块做填充的,填充的方式也有很多种,在解题时,需要考虑这一点。

例题三:

查看加密代码,发现是CBC链式加密。这里的块加密算法是异或,块长度为16。

可知:

Pn ^ Cn-1 ^ Key = Cn

所以:Key = Cn ^ Cn-1 ^ Pn

得到Key后,就可以从最后一块明文和密文,逆推出所有明文。这里需要指出,第一块明文是得不到的,因为我们不知道初始向量iv的值。

但是这里Pn是不确定的,因为算法会对明文做填充。但是不要紧,我们可以爆破,遍历所有可能的填充位数,从1位到16位,得到明文的最后16个字节,得到不同的Key,算出不同的明文。

代码:

def xor(a, b):

returnbytearray([i^j for i,j in zip(a, b)])

with open('enctypted.txt', 'rb') as f:

c = bytearray(f.read())

last = bytearray("Please make sure keep this secretsafe.\n")

for i in range(16):

padding_len = 16- i

plain =last[-(16 - padding_len):] + bytearray(chr(padding_len) * padding_len)

k =xor(xor(c[-16:], c[-32:-16]), plain)

res =bytearray()

for i inrange(len(c), 0, -16):

tmp =xor(c[i-16:i], c[i-32:i-16])

res =xor(tmp, k) + res

print res

print"\r\n================\r\n"

本文分享自微信公众号 - 湛卢工作室(xuehao_studio)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 3.2.9 、Google Tag Manager实战指南——广告参数传递

    通常需要区分不同渠道来源的时候一般会使用UTM标记,其实还有另一种方式可以传递广告参数,这个需要使用GTM,同时配合自定义维度来使用。

    GA小站
  • SpringBoot整合mybatis一直失败差不到数据,解决方案

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    多凡
  • 3.2.8 、Google Tag Manager实战指南——虚拟页面跟踪单页应用

    单页应用就是指浏览器访问站点的时候,使用期间不会重新加载页面,给用户的看到的是所有URL就是一样的,有体验原生APP的感觉,这是一种从Web服务器加载的富客户端...

    GA小站
  • SpringMVC @RequestMapping 注解

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    多凡
  • 前端基础知识:Web开发的26项基本概念和技术总结

    Web开发是比较费神的,需要掌握很多很多的东西,特别是从事前端开发的朋友,需要通十行才行。今天,本文向初学者介绍一些Web开发中的基本概念和用到的技术,从A到Z...

    用户5827212
  • 爬取搞笑视频

    最近小编经常刷知乎上的一个问题“你见过哪些是「以为是个王者,结果是个青铜」的视频或图片?”。从这个问题我们就已经可以看出来里面的幽默成分了,点进去看果然是笑到停...

    HuangWeiAI
  • Mitaka:针对开源情报收集任务的浏览器扩展

    今天给大家介绍的是一款针对开源情报收集任务的浏览器扩展,这款扩展名叫Mitaka,希望该工具可以给广大研究人员的OSINT搜索研究提供帮助。

    FB客服
  • 爬虫篇 | Python学习之Scrapy-Redis实战京东图书

    scrapy-Redis就是结合了分布式数据库redis,重写了scrapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。

    叫我龙总
  • Centos7源码编译安装PHP7.2(生产环境)

    注:本文转自详解Centos7源码编译安装 php7.2之生产篇,如需转载请注明出处https://www.cnblogs.com/zhuchenglin/p/...

    lin_zone
  • cas-client单点登录客户端拦截请求和忽略/排除不需要拦截的请求URL的问题

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    eguid

扫码关注云+社区

领取腾讯云代金券