Discuz教程网

[应用中心] Discuz 插件如何通过应用中心审核,基础PHP开发安全

[复制链接]
authicon dly 发表于 2013-7-24 21:24:54 | 显示全部楼层 |阅读模式
基础PHP开发安全Part 1: addslahes/daddslashes
addslahes是PHP自带的一个函数,其转换规则为
        \   => \\
        '   => \'
"   => \"
        NUL => \0
        其中关键在 ' 和 " 上,这两个过滤非常重要,从某种角度说,这个过滤已经回避了很多SQL注入和PHP入侵。
        在最新的Discuz!X系列中,$_GET等都未经过addslashes,如果在程序中直接用DB::query执行SQL查询,并且没有使用%s,那么您需要进行必要的检测。
example 1: Intval
<?php
...
$num=intval($_GET[‘num’]);
DB::query(“UPDATE %t SET `text`=’{$num}’ WHERE uid=’{$_G[‘uid’]’}”, array(‘pluginx_test’));
?>
example 2: String
<?php
$str=addslashes($_GET[‘str’]);
DB::query(“UPDATE %t SET `text`=’{$str}’ WHERE uid=’{$_G[‘uid’]}’”, array(‘pluginx_test’));
?>
example 3: Intval via %d
<?php
DB::query(“UPDATE %t SET `text`=%d WHERE uid=%d”, array(‘pluginx_test’, $_GET[‘num’], $_G[‘uid’]));
?>
example 4: String via %s
<?php
DB::query(“UPDATE %t SET `text`=%s WHERE uid=%d”, array(‘pluginx_test’, $_GET[‘str’], $_G[‘uid’]));
?>
但通过数据库/数据表类的入库方法(::insert, ::update)入库时,这些函数会进行必要的处理,所以,请不要进行重复处理。
example 5: ::insert
<?php
$str=$_GET[‘str’];
$str=addslashes($_GET[‘str’]);
DB::insert(‘pluginx_test’, array(‘uid’=>$_G[‘uid’], ‘text’=>$_GET[‘str’]));
?>
example 6: ::insert
<?php
DB::insert(‘pluginx_test’, array(‘uid’=>$_G[‘uid’], ‘text’=>$_GET[‘str’]));
DB::insert(‘pluginx_test’, array(‘uid’=>$_G[‘uid’], ‘text’=>addslashes($_GET[‘str’])));
?>
同时补充,如果您的插件中使用了iconv等编码转换函数,特别是从UTF-8转成GBK这类情况,并且您需要addslashes,请记得先转换编码,再进行addslashes过滤。因为在UTF-8中的/, \, ‘, “”等敏感符有七八种表示方式,大多不会被addslashes处理,只有先转换编码后才能开始安全处理。此外,urldecode等也在此列。
Part 2: 从引号开始
一般来说,ASP常常比PHP容易入侵,很大程度是因为很多ASP初学者,对数据库的安全过滤一概不知,我们常常可以看到下面的这种语句(假设members有三列, id username password,实际需要时可以穷举列数尝试攻击)。
example 7: Without a quote
<?php
$query=DB::query(“SELECT username FROM members where id=$id”);
?>

如果$id可以为人为构造的string,则可能有以下语句:
令 string $id = 9999999999 union (all/distinct) select password FROM members where id = 1
SELECT username FROM members where id = 9999999999
union select password as username FROM members where id = 1
理论上可以获得id=1(常常是管理员)的密码,虽然打开 Discuz!的SQL安全机制后,以上攻击失效。
于是,非常重要的,你必须加上一个引号,并且保证 $id 是int类型,或者已经被有效的addslashes,包括数字类型。
example 8: With a quote
<?php
$query=DB::query(“SELECT username FROM members where id=’$id’”);
?>
目前所知,并没有有效的办法攻破上述安全处理。
国内安全组织80vul特别提醒,in()/limit/order by/group by 这四个地方都是容易忘记加引号的,在本审核员两个月中的审核中,有一款插件因为in()轻信了dimplode的addslashes处理,忽视了此处仍容易被注入,而被打回。
timestamp time() date()
Part 3. 引用PHP代码文件
首先我们引入一段令人非常无语的代码作为例子。
example 10:
<?php
require $_GET['path'].'.php';
?>
简直太容易了,如果可以打开URL,我就可以执行远程脚本了。
http%3A%2F%2Fwww.example.com%2Fx.txt%00
http%3A%2F%2Fwww.example.com%2Fx.txt?
http%3A%2F%2Fwww.example.com%2Fx.txt#
%00(NUL) ? # 都可以做URL的截断符,使得后面的.php不影响。
在此特别重要的警示,绝对不要写出这样的代码,否则可能会发生非常多的情况,在此建议几种可行的方法
example 11:
<?php
if(!in_array($_GET['path'], array('zip','bond','download'))) exit;
require $_GET['path'].'.php';
?>
example 12:
<?php
if(!in_array($_GET['mod'], array('index', 'add'))) $_GET['mod']='index';
?>
另外,一些插件需要引用一些PHP文件,这些PHP文件的名字并不确定,例如7ree的某漫画插件中,需要引用某名字的PHP文件,这个文件记载了漫画的信息,而这个名字由客户端提供,不能保证安全性。几次打回,分别是由于没有处理../和%00,最后要求使用白名单的方法来解决,即只容许某个范围的字符输出。preg_match的\w一般可以满足要求,建议大家参考。
Part 4. GPC过滤
大概的情况就是,对于一个外部变量,你必须有准确的限制。该是数字的,保证它是数字。该是正常文字的,保证它是正常文字。这里很大程度上是为了防止数据库注入及XSS。
intval 整数
特别注意,用intval处理一个非数字的字符串,仍然可以得出一个数字的结果, 比如intval("abcdefg+123456abcdefg-1111"); 结果为123456。        
因为intval的原理是:从左边第一个数字(或正负号)开始,一直读到数字结束。
还有特别要注意的,intval不会解决负数的问题,建议再进行 > 0 的检查
is_numeric 数字
请特别注意,如果你只允许正数的话,就不要使用这个函数,因为他可能产生好长好长的一个数字串,好像造成了PHPWind的一次运行漏洞。
addslashes 可能要进库的字符串
addslashes是非常重要的一个过滤,尤其要注意,不要重复过滤。
strip_tags 对于不应该有HTML的文本
这主要是 XSS 的要求,避免被 XSS ,重点就是避免用户的HTML标签直接输出到浏览器上。
htmlspecialchars 对于可能有HTML的文本,但是不希望这些代码被解析。
同strip_tags(),有区别的是,他不会去掉标签,而是变成可阅读的样式。
MySQL的安全问题Part 5. 涉及搜索的插件,使用LIKE匹配时的注意
许多涉及搜索的程序都会忘记下面这两个字符的过滤:
        % _
% 相当于 .*    _ 相当于 .+
Discuz! 的过滤代码: addcslashes($keyword, '%_')
Part 6. 加减法问题,防止积分暴涨
对于一个unsigned int,如果你把0再减去1会怎样呢?是的,他会变成四十二亿多(1 << 32 -1)
                UPDATE test set num=num-1                        (此时ccc=0)
                        num=4294967295
该漏洞曾经发生在 PHPWind 的天使宠物插件上,用户现金为零时,打怪遇到惩罚事件掉金钱,结果直接获得高额的奖励。
                UPDATE test set ccc=ccc+(-1)                (此时ccc=0)
                        num=4294967295
再次变成四十二亿多
对可能为负数的数字,加一个引号,可以解决这个问题:
                UPDATE test set ccc=ccc+'-1'                (此时ccc=0)
                        ccc=0 没有发生变化
著名插件作者 虾米:对于mysql建表的时候,数字不代表你使用的数字最大的位数,例如 timestamp int(10) unsigned,这一个大家应该非常熟悉了,这一个括号中的10并不代表timestamp这个数字最多只能十位(具体的意思自己查手册吧,不知道也无妨),如果不知道是否应该用unsigned的时候就不要用unsigned的,涉及到积分的字段全部不要使用unsigned的(安全起见,宁可让用户负分也不能是正的无穷大)
Part 7. 复杂的MySQL问题(来源于80vul)
两个MySQL截断问题,第一个是超长截断,第二个是加一个0xc1截断(UTF8)。
我们用例子来说
example 9:MySQL Problem #1
MySQL:
CREATE TABLE IF NOT EXISTS `pre_test` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(13) NOT NULL,
  `password` char(32) NOT NULL,
  PRIMARY KEY (`uid`),
  UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2;
INSERT INTO `pre_test` (`uid`, `username`, `password`) VALUES
(1, 'admin', 'aeb0908baa5b97e574c5b1825d8f4b92');
<?php
if(!DB::fetch_first(“SELECT * FROM pre_test WHERE username=%s”, array($_GET[‘username’]))){
DB::query(“REPLACE INTO pre_test set username=%s, password=%s”, array($_GET[‘username’], md5($_GET[‘password’])));
}
?>
我现在用username=” admin                            lalala”
password=’123456’来注册,那么fetch_first处执行的查询是
SELECT * FROM pre_test WHERE username='admin                            lalala'
查询结果: MySQL 返回的查询结果为空 (即零行)。
零行的原因:因为数据库中的username不可能那么长!肯定找不到对应的!
下面DB::query得以执行,
REPLACE INTO pre_test set username='admin                            lalala', password='e10adc3949ba59abbe56e057f20f883e'                                
查询结果:影响了 2 行。插入的行 id: 2 ( 查询花费 0.0003 )
当长长的username进入数据库时,超长部分自动被截去,空格又被忽略,使得username与admin相同。
当然这种方法并非完美,admin的uid已经发生了变化,这意味着创始人身份无法延续,版主的身份也会丢失,但该用户可以是管理员,至少已经拥有了管理员的名字。
其实,如果你不用REPLACE INTO,这个问题就可以小很多,这样顶多造成MySQL Erro因为一般情况下username是UNIQUE索引的,INSERT INTO不会正常执行。
PHPWind就曾经在这件事上摔跤过,当时是用第二个方法,也就是我们即将介绍的,0xc1截断修改任意用户密码(0xC1不是四个字,而是一个字符)。在UTF-8中,0xC1会使MySQL截断后面的数据。当我们请求的用户名“甜橙”后面加上0xC1,那么包括这个字符,及其之后的,在插入时都会被忽略。我们来模拟下。
SELECT * from test where username='甜橙'
查询结果:        甜橙         864411
SELECT * from test where username=concat('甜橙', 0xc1)
查询结果:MySQL 返回的查询结果为空 (即零行)。
REPLACE INTO test set username=concat('甜橙', 0xc1), password='123456'
(模拟PHPWind早期的注册)
甜橙密码被修改:        甜橙         123456
简单说,执行一个查询,可能结果为空,但是一会插入信息时,却实际覆盖了原有的数据。使用REPLACE INTO时,开发者要特别小心。
高级PHP开发安全Part 8. 字符串可能是数组
非常少见的一个问题,但提出来吸引眼球。
example 13:
<?php
$s='gnaojgapjhpajgpa';
?>
此时 $s[1]=='n'
example 14:
<?php
$s=array('nbaohpas', 'gkhaopjgpaj');
?>        
此时 $s[1]=='gkhaopjgpaj'
如果你要获得用户请求的数据的某一个字节,并且你考虑用[1]这样的方法来取,而不是substr,那么一定要用户给的数据是不是一个字符串,否则,你可能「拿」到一个字符串、一个数组等等……
解决方法:检查是不是字符串,或者干脆就变成字符串好了。也可以用{1}来取。
example 15:
<?php
$s=array('nbaohpas', 'gkhaopjgpaj');
$s=(string)$s;
?>
此时 $s{1}=='r',因为$s=='Array'
也可以直接拒绝这类非法的信息
example 16:
<?php
if(!is_string($s)) exit('xxxxx');
?>
Part 9. 随机数的可靠性
由于rand函数可能只有三万多种变化,一般不适合用于有认证性的场合,首先,我们建议无论如何使用mt_rand代替rand.
不要用随机函数去创造你认为安全的随机数,一个简单的原因,随机数的产生是根据种子的,种子相同时,从生成种子到第n次求随机数的结果都是完全相同的。考虑到PHP的多个版本存在随机数问题,Discuz!某版本后,决定采用md5散列函数来进行随机,random()函数回避了许多问题,建议替换:
string random(int $length, bool $numeric = 0)
在产生安全认证级别的密串时,这可能是一个大问题。某版本的Discuz!论坛的找回密码功能曾被破解,用于重设任意用户的密码。
如果你要获得用户请求的数据的某一个字节,并且你考虑用[1]这样的方法来取,而不是
Part 10. CSRF
CSRF有两个特点:普遍、有害。绝大多数的插件,刚注意到CSRF的情况时,往往有许多作品需要大范围地改写,而且它具有有害性,即凡是CSRF攻击,往往都大大小小有利用价值,但是往往不会造成数据库一类的严重安全问题。
首先,我们先讲CSRF在普通用户范围的存在意义,我们先举一个例子,假设有一个版主投票插件,每个人点击“支持“按钮,该版主获得一票,也可以投“反对”票。
<a href="plugin.php?id=test:index&vote=225290&op=support" class="btn">支持</a>
<a href="plugin.php?id=test:index&vote=225290&op=against" class="btn">反对</a>
该版主为了争取选票,他可以在帖子(甚至签名)中嵌入以下的代码:
        
一旦访问这个帖子,img中的链接即被访问一次,用户在“不知不觉”中投给了他一票。
反对这个版主的人也有办法,他可以在帖子(甚至签名)中嵌入以下的代码:
        
一旦访问这个帖子,同理,用户在“不知不觉”中投给了他一票反对。
我们当然知道,这种问题并不会造成非常大的影响,但当我们关注了足够多的严重安全漏洞后,这些小小的漏洞是否值得注意呢?它们的确影响到了插件的初衷。
我们接着讲一种面向管理员的CSRF,这种方法往往需要一定的运气,后台程序也有可能,但是后攻击难度太大。
假设刚才的投票插件,有一个前台管理面板,里面自然应该有一个“删除该候选人”的功能,我们假设一下。
        225290        xxx        xxx票支持 xxx票反对        查看投票数据 | 删除该候选人
我们假设“删除该候选人”是这样的一个链接
<a href="plugin.php?id=test:cp&action=del&uid=225290">删除该候选人</a>
假设前台面板是这样的执行代码:
        if($_GET['action']=='del' && $_G['adminid']==1 && ($uid=intval($_GET['uid']))>0){
                ……
        }
这样的话,有一种非常需要运气的方法,只要让有权限的用户访问到“plugin.php?id=test:cp&action=del&uid=225290”这个链接,即可将225290这个候选人删除掉,这里对管理员权限有要求,但是这是可以实现的,实战中有一个例子:
版块:站务管理
标题:管理员进来!网页有错位!
内容:…….
一般稍有管理的论坛,都会有管理员级用户访问。管理员可能永远不知道攻击是如何发生的,但是他已经实施了删除操作。这就是典型的面向管理员的CSRF攻击。
特别是删除操作,往往判断不足,使这个便成了高热度的情况。
修补建议:
1. 使用formhash
formhash是比较简单有效的一种方法,比如在链接中加一个formhash={FORMHASH}就有效果,在模板语法中,我们可以这样写
<a href="plugin.php?id=test:cp&action=del&uid=225290&formhash={FORMHASH}}">删除该候选人</a>
Discuz! 0629补丁所修复的一个安全问题涉及security key的取出,formhash的安全性被降低,在了解指定用户的情况下可能制造formhash,但是利用0629漏洞的例子还比较少,这个漏洞有远古性,0day工具也没有释出,了解对方环境有一定难度,所以formhash还是可信的。请放心使用这个方法!
2. 使用submitcheck()
submitcheck有两种好处,一种是确定通过POST的方式提交了一个变量,另一种是对来源地址有审查,也就避免了站外的访问威胁。这在表单中,我认为是有必要添加的。submitcheck同时也会检查formhash,并且防止FLASH攻击。
一般是在添加/删除表单使用的。例如有一个添加候选人的表单,提交按钮的name="addsubmit"。
PHP代码中可以这样写:
        if(submitcheck('addsubmit')){
                ….
        }
        这样,提交时我可以肯定四个要点:POST提交,有FORMHASH,来源有限,不是FLASH,安全性能提高很多。如果要对删除操作进行这个检查,通常是通过弹出窗口进行confirm,用formhash的安全性也能满足一般需要。
Part 11. ceil round floor的区别
一般是针对积分兑换的插件,我举一个例子,如果规定50积分可以换一个某奖品。
                $a['奖品']=$func($a['积分']);
                使用 ceil 函数,我可以支付51个积分,换两个(进一法)
                使用 round 函数,我可以支付75个积分,换两个(四舍五入)
                使用 floor 函数,我只能至少支付100个积分,换两个(舍去法)
这是三个取整函数,特别注意根据实际需要,使用适当的函数,避免让一些用户破坏了积分兑换的公平。或者,你的程序应该自己判断,使用户提供的积分中不足额的部分不被扣减。
在此处,intval相当于 floor,也可以。
Part 12. 不要上传SWF
FLASH CSRF已经成为非常恐怖的攻击力量,在这里的建议是 不要允许用户上传SWF格式的文件,一旦这种文件上传到论坛上,就可以引发FLASH CSRF攻击。
这种攻击的威胁非常巨大,如果普通用户访问,可以攻破FORMHASH和submitcheck(),如果管理员访问,可以攻破!defined('IN_ADMINCP') && die;
原理就是利用FLASH请求数据,并且发出getURL的请求,或者javascript到浏览器上进行攻击,FLASH可以实施的攻击比较强。若自己的网页有必要展示swf时,请审慎控制allowScriptAccess参数。
Part 13. 不要Foreach空值、数字、字符串
foreach一个空值或一个数字时,会报一个错误,这可能暴露路径,但新版中已对PHP错误报告做了处理。开发者需要在不引用Discuz来初始化的一些文件中注意。万一foreach一个不合格的内容时,会中断程序运行,使得程序执行不正常,这要注意。
Part 14. SESSION认证的验证码常见问题
        并不推荐使用SESSION来做验证码,经过加密的cookie可以符合要求(例如使用Discuz!的authcode函数)。但SESSION也有其独特的优点,在这里我对用SESSION进行验证时,一种容易忽略的情况进行说明:
example 17:<?php
...
if(submitcheck(....)){
if($_SESSION['code']!=$_GET['seccode']) showmessage(...);
...
}else{
...
session_start();
$_SESSION['code']=random(4, 1);
...
?>
        这里出现一个问题,若用户不接受SESSION,那么在submitcheck时,$_SESSION['code']为空,seccode为空时绕过。即是,如果你用SESSION进行验证,一定要检查SESSION是否真的存在并且有效,避免直接使用SESSION的存储值而忽视SESSION是否存在的问题,使得攻击者可以轻易绕过验证。
DX论坛及插件机制特色Part 15. 伪造IP
伪造IP是从很久以前就有的一个安全问题了,绝大多数的网站都不能正确处理,使得该伪造方法可以畅通无阻。如果有需要发敏感文章,那么通过伪造IP,甚至可以让人做替罪羊。
我们来看一下 Discuz! X 判断用户IP的方法。
private function _get_client_ip() {
                $ip = $_SERVER['REMOTE_ADDR'];
                if (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
                        $ip = $_SERVER['HTTP_CLIENT_IP'];
                }elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
                        foreach ($matches[0] AS $xip) {
                                if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
                                        $ip = $xip;
                                        break;
                                }
                        }
                }
                return $ip;
        }
我们可以看到, 获得IP的优先级是 CLIENT-IP、 X-FORWARDED-FOR, 这两个是在 HTTP 头特别指定的量,相当于“识别真实IP”,这是在中国应用非常广泛的代码。
但是,逆向过来看,你如果提交 X-FORWARDED-FOR: 127.0.0.1 ,Discuz就会认为你是来自 127.0.0.1 的用户,难道我说什么,DZ就信什么?你还真说对了。已确定 Discuz! 除了限制代理访问之外,没有其它方法阻止此类假IP。此方法可用于刷Discuz!的访问推广的积分。
        开发者编写投票插件,建议使用以下的代码进行判断,使得有代理迹象的投票全部无效。
                        if($_SERVER['HTTP_X_FORWARDED_FOR'] ||
                                ERVER['HTTP_VIA'] || $_SERVER['HTTP_PROXY_CONNECTION'] ||
                                ERVER['HTTP_USER_AGENT_VIA'] || $_SERVER['HTTP_CACHE_INFO'] ||
                                ERVER['HTTP_PROXY_CONNECTION']) {
                                        这是代理投票,在此填你的处理代码
                        }
Part 16. 后台模块为什么一定要加IN_ADMINCP
在X版本中,在插件目录下的任意以 .inc.php 结尾的文件都可以通过 plugin.php?id=xxxx:xxxx 的形式访问,不管这个文件有没有登记为模块,以及是否是后台模块,也可以通过这种方法被访问。
我们假使有这样的一个插件,它的目录分布如下:
        template                        模版目录
        admincp.inc.php                后台管理文件(包括修改、结单、删除等功能)
        index.inc.php                主程序(包括插件数据的初始化,cp.inc.php的权限判断)
        install.inc.php                安装文件(在XML中已登记)
        uninstall.inc.php                卸载文件(在XML中已登记)
        cron_payrecord_bakup.inc.php        计划任务文件(由install.inc.php在安装时复制到cron文件夹中,请注意这种做法已经不再允许)
登记的插件模块
        管理中心        admincp.inc.php        支付管理
        主导航项目        index.inc.php        主导航项目
我下面给出几个链接
        plugin.php?id=xxxx:admincp
        在admincp.inc.php没有IN_ADMINCP或者强制权限判断的情况下,我可以通过这个程序进行一些关键的操作(修改、结单、删除),其中的formhash和submitcheck都可以被绕过。除非碰到showformheader等函数被迫终止。
        plugin.php?id=xxxx:cp
        该程序的一些变量没有经过index.inc.php初始化,可能功能失常。当权限判断由index.inc.php承担时,权限判断被绕过。
        plugin.php?id=xxxx:index
        正常访问的连接
        plugin.php?id=xxxx:install
        插件进入安装过程,但因为runquery函数是在function/plugin中定义的,平时不被引用,反而很难清空数据库。提议安装文件严格命名为install.php
        plugin.php?id=xxxx:uninstall
        同xxxx:install,一般不能进行卸载攻击,但是也可能造成其他风险。
        plugin.php?id=xxxx:cron_payrecord_bakup
        运行计划任务程序,咋看问题不大,但是有两点。一些计划任务有明确的时间性,这个连接可能使计划任务在短时间内被重复运行,类似定期发奖类的可能混乱。第二,计划任务很多比较消耗资源,通过计划任务实施DDOS或者CC攻击,达到的效果会快而且好。
        重申不再允许插件安装时复制/移动文件到论坛程序文件夹,X3应该直接放在插件的cron目录下,X2.5应该同时“合并安装”一个计划任务扩展。
建议:后台管理文件一定要记得加 IN_ADMINCP,无需前台访问的文件不要加.inc.php
Part 17. 敏感词过滤
大家对Discuz! 的那个敏感词过滤不应太有信心的
游客,如果您要查看本帖隐藏内容请回复
插件中,如果有推广类信息,并且附带有敏感词过滤功能的,建议作考虑,不允许用户使用统一码表示汉字。
Part 18. 手机版和打印版改变了局面
近年出现很多验证类的插件,经过调查,多数没有抵御灌水机的能力。因为较新的灌水机都会优先使用wap手机版页面进行灌水,这样可以绕掉验证类插件的电脑版嵌入点。
由于有用户投诉,后期统一对没有针对手机版设计嵌入点,又声称用于防灌水的验证类插件全部给予打回处理。只是用于检查用户提供的手机是否有效的这一类验证,都放宽。
之前有很多回复限制类插件,限制用户需要达到一定的条件才能访问帖子。有登陆看全文的,也有回复看全文的。暂不提其中一些产品严重影响了搜索引擎的收录,并且可能被指出是欺骗。这些插件很多在打印版/手机版/archiver下失败了,显然嵌入点少了,难以实现这些功能。
我们建议这类插件能设法showmessage来屏蔽打印版,同时在$postlist里面先下功夫,这样在archiver下应该可行。
Part 19. 网址路径书写不合理
        近期有多款插件/风格因此被打回,开发者具体的测试环境可能有所不同,使得开发者debug时的显示效果和全部用户网站上的显示效果有差异,网址路径书写不合理是其中的一种。
第一种情况是:路径使用反斜杠
        正确:<img src="source/plugin/test/static/test.jpg" />
        错误:<img src="source\plugin\test\static\test.jpg" />
并非所有的服务器都接受反斜杠,请不要用反斜杠书写路径。
第二种情况是:路径前加一个/,使路径从根目录开始
        正确:<img src="source/plugin/test/static/test.jpg" />
        错误:<img src="/source/plugin/test/static/test.jpg" />
当用户的BBS并非安装在根目录时,图片文件不能被正确引用。
Part 20. 调用远程信息的可靠性过滤
        开发一些插件时,我们会通过客户端的页面向我们插件的服务器端发送一些信息,例如站长信息,信息调用,或者功能请求。一个常见的例子便是更新信息。诚然,我们放心自己不会往自己的服务器端挂马,但是如果域名被劫持,或者发生这一类的事故,那么利用者就可能通过这个插件后台端向使用者发送攻击信息。Discuz!某版本的挂马,同时利用了后台的一个执行脚本的漏洞,以及custom.discuz.net的劫持。
        因此,尽管向用户端发送的信息由我们控制,我们依然希望里面没有有害代码。这是一个提议,建议服务器端发送的代码,尽可能由不需HTML标记的代码组成,以便htmlspecialchars处理。或者,直接使用iframe的方式引用,较新的浏览器都有良好的权限控制,使得框架内的代码无法控制框架外的主页面。请用iframe而不要用javascript,后者可以跨站。
Part 21. IN_DISCUZ的必要性
        至今仍有不少插件由于缺少必要的IN_DISCUZ而被打回,缺少IN_DISCUZ的程序被单独执行时,往往由于没有初始化函数,而报出fatal error泄露服务器路径。实际操作中,确可无需IN_DISCUZ的类文件和函数文件并不做强制性要求,但建议只要是可以加这项限制的,都应该加,这样比较合理。
Part 22. 认证类密串的生成问题
        有时,为了校验用户的身份,或者验证用户的权限,我们会通过网址传递一个hash,hash正确的才执行相关代码。
        plugin.php?id=xxx:xxx&ac=openimg&id=1893&hash=b95970a598bc1da9af743f988d9b0ee0
        其hash的生成算法是
<?php
...
$hash=md5('*cOpYRight*'.$imgid.'|'.$_G['uid'].'|'.$_G['siteurl']);
...
?>
        从生成的md5来反推原文基本是不可能的,但是生成这样一个md5却容易。即使你的插件使用了Zend加密,至今依然有各种方法可以反编译,并了解到你生成hash的算法。由于算法与论坛相关的部分只有siteurl,而siteurl很容易得到,所以这个hash实际上可以由客户端自行生成。
        显然,要解决这样的问题,我们要让这个hash不容易生成,即使知道加密算法。我们可以在md5的参数中增加$_G['authkey'],或者直接用formhash($imgid) 也是可以的。
        一般的认证场合中,直接使用FORMHASH也可以,除非这个hash不止需要与用户的身份有关,例如还与附件的ID有关,等等。
Part 23. 多编码版本的程序编码问题
        目前,大多数的插件都使用语言包编写,并且支持多编码。这就要求汉字和汉字符号,都应该写在语言包内。最近有不少插件,在php文件中仍然有非注释部分的汉字和汉字符号,都让作者拿回去改,或者用统一码&#xxxx;这样的来解决。这样,不同编码的用户才可以真正看到相同的效果。
Part 24. SWF决定奖励的抽奖插件
        许多抽奖插件给论坛带来了许多娱乐享受,这些插件有很大的缺口,我觉得有兴趣的开发者可以试试看。有不少的抽奖插件由swf抽奖,决定奖品后发送给服务端,由服务端发奖。这就涉及到了解swf与服务端的通讯方式后,恶意刷奖品。我们提醒,swf发奖插件应该由php决定奖品,swf仅负责显示奖品。
Part 25. 时间安全问题
        在TIMESTAMP常量产生后,Discuz!会进行timezone_set,此后用date(),time(),gmdate(),dgmdate()获得的时间由系统及用户共同决定,除非你使用date(..., TIMESTAMP) gmdate(...,TIMESTAMP)等。其中,用户设置的时区可以远远超过24小时。如果插件中用于判断时间的代码没有考虑到这个问题,就可以使用户穿越时空。例如一款签到插件,在早期某版本中,许多非开发者用户已经知道通过调整时差,就可以向前一天或者向后一天。而实际上,如果该用户通过DOM编辑器等方式,提交了一个超过24小时的时差,那么该用户可以穿越到任何一天,这就使得签到用户可以补签之前任何一天的记录,也可以签到未来的日子。
        同样的问题如果出现在银行插件,那么后果会更加严重。可喜的是应用中心唯一一款银行插件,使用TIMESTAMP之间的对比,这样就回避了这类问题。

文件下载:
游客,如果您要查看本帖隐藏内容请回复




上一篇:Discuz X2.5为什么我的头像显示不了,也改不了
下一篇:1314学习网签到记录贴-2013年7月25日
authicon 网络摆渡客 发表于 2013-11-12 22:39:53 | 显示全部楼层
每个都要回复哦
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1314学习网 ( 浙ICP备10214163号 )

GMT+8, 2024-5-19 22:24

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表