[自己动手改wordpress.3]给wordpress加上禁止附件外链

前言,

有几个月没写php了. 刚好公司有个web的项目需要改下之前他们搭建的Wordpress的站点, 因为给所有的page/post都加上了权限组来控制权限和SSO登录认证一体化. 但是附件却没有保护到. Wordpress直接暴露了附件的服务器上的地址.作为一个安全隐患, 这个需要修复.

本文适合有一些WP的插件开发经验的朋友.

1,数据准备,

WP的附件attachment存放在Wordpress的后台的Media这个页面来控制,也可以直接通过发page的时候添加媒体文件来实现.

数据库存放在wp_posts 和wp_post_meta, 字段是`post_type`=‘attachment‘

也就是每当你需要发一篇文章的时候, 当你点击了一次上传媒体文件, Wordpress会存一个post到wp_posts表和wp_post_meta表. 一个文件对应一次数据的插入.

其中wp_post_meta表有4个字段, 分别是meta_id, post_id, meta_key, meta_value

meta_key=‘_wp_attached_file‘ 的数据就是我们的媒体文件了, 对应这条数据的meta_value是一个包含了上传时间和文件名的字符串.

而在wp_posts表, 对应的post_id的就是这个媒体文件的post的数据, 这里面我们最主要关心的是post_type字段和post_mine_type字段. 这里我上传的是一个mp3文件, 所以

对应的数据如下.

2, 思路拓展

要改变文章里面的数据, 就有几种方案. 通过上面的数据, 可以看见Wordpress是把一个相对或者绝对的路径存到了数据库. 这个地址就暴露在了外面. 在发文章的时候, Wordpress同样也存入了这个地址.

假定我们的域名是cnwp.com, 现在想实现的是所有的附件链接都最终指向cnwp.com/download.php?fdi=xxxx来控制文件下载.

这导致了如果你只修改了media的相关数据, 页面page里面存入的链接是已经保存下来的, 没有被改掉.

通过阅读源码, 发现Wordpress的insert post在插入post数据的时候, 如果判断到有attachment附件的数据插入, 就会提前去调wp_insert_attachment的函数. 所以这里可以使用apply_filter来过滤insert的数据. 但是我们是在插件里面来调, 钩子在获取数据的时候数据已经提前插入了. Wordpress没有提供修改插入附件的filter给我们使用. 这样修改了原始的数据, 实现是比较麻烦和复杂. 这条路比较难. 现卡在这里. 于是又去阅读了一下Wordpress其他的下载管理的插件,
做的比较好的是WPDM, download manager这个插件, 但是这个插件最终也是暴露了实际的下载地址. 它只是把按照日期目录排的文件移到了它自定的一个位置. 实际是一样的. 还是收费的. 而且对于之前的权限组不能控制到这个链接. 只控制到了post的content的数据. 所以也放弃.

查阅到了the_content和wp_get_attachment_url 两个filter, 一个是在控制page的content内容, 一个是控制attachment附件页面的url.

前面说过, WP每次在上传附件的时候, 实际上也发了一条post的数据, 那么既然是post的数据, 就有一个地址, 这个地址就是 http://cnwp.com/xxx/?attachment_id=103 类似的地址,xxx是network sites(WP的多站点子目录模式, 还有多站点子域名模式, 类似xxx.cnwp.com), 这个地址就是attachment附件的页面.

所以我们要改的就是改掉页面上所有attachment的链接, 不管它在一个页面里面是以http://cnwp.com/xxx/?attachment_id=xxx的形式出现,

还是以http://cnwp.com/xxx/wp-content/uploads/sites/6/2015/01/Yanni-Ninghtingale.mp3  这样的链接出现.

我们要保证后一个url的实际的文件地址,不会出现在用户的面前.

3,  首先去插件页面, 加filter,

array($sc, ‘scAttachmentRewrite‘) 这个是调的我的一个类, 里面有一个scAttachmentRewrite的函数, 后面的10是优先级, 1是the_content会传入进scAttachmentRewrite函数的参数

同理,scAttachmentUrlRewrite也是类似的一个.

再来看下函数. 这个函数是来重写the_content的url的, 通过正则来分段匹配出数据. 然后获取到页面的post的id, 这个post_id是和相关的权限组作了绑定, 这里我取出来是为了方便后面处理. 通过scUpdateAttachUrl函数来更新或者插入一个自定义的关联表, 里面存的是一会儿要重写的url的id, 这样就可以通过这个id来作新的替换掉的id的值

    /**
     * Rewrite the content attachment links.
     *
     * @param string $content theContent.
     *
     * @return mix.
     */
    public function scAttachmentRewrite($content)
    {
        $pattern = "/<a href=\"(\S+)(\/wp-content\/uploads\/sites\/)([0-9]+)(\S+)\">/";
        preg_match_all($pattern, $content, $match);

        // $match[0]  all links
        // $match[1] link head  http://cnwp.com/xxx or http://xxx.cnwp.com
        // $match[2] replace content /wp-content/uploads/sites/
        // $match[3] site num
        // $match[4] file meta_value
        if (isset($match) && is_array($match)) {
            $post_id = get_the_ID();
            $old_links = array();
            $new_links = array();
            foreach ($match[1] as $k => $v) {
                $old_links[$k] = $v.$match[2][$k].$match[3][$k].$match[4][$k];
                $_snx_id = $this->scUpdateAttachUrl($match[3][$k], $match[4][$k]);

                $rand_string = $this->scRandString(10);
                // s=site no, id=sc attachment_id, p=page/post id
                $param = $rand_string.base64_encode('s='.$match[3][$k].'&id='.$_snx_id.'&p='.$post_id);
                $new_links[$k] = $v.'/sc_download.php?fid='.$param;
            }
            array_walk($old_links, array($this, 'scArrayWalks'));
            $content = preg_replace($old_links, $new_links, $content);
        }

        return $content;
    }

scAttachmentUrlRewrite处理的流程和下面的函数基本类似. 唯一不同的是因为是单个附件的page, 所以需要处理掉图片,音频等直接呈现在用户面前的url, 保证它们不会被重写掉. 所以就需要用获取到的post_id和这个attachment的页面的数据去查询post表里面post_mime_type=attachment的数据,排除掉media,png,gif等的数据.保证只重写对应格式的.

其中的代码片段, 其他和上面一样, 不过wp_get_attachment_url传入的参数是一个url地址, 正则就稍微不一样一点儿,

        $pattern = "/(\S+)(\/wp-content\/uploads\/sites\/)([0-9]+)(\S+)/";

scQueryMediaMeta就是去查询的对应的post数据. 进而用post_mime_type来对比. 如果有其他格式的, 大家也可以自己加对应的

                $checkMedia = $this->scQueryMediaMeta($match[3], $match[4], $post_id);
                if (!$checkMedia) {
                    return $url;
                } else {
                    if (isset($checkMedia['post_type']) && $checkMedia['post_type'] == 'attachment' && isset($checkMedia['post_mime_type']) ) {
                        $checkApp = strpos($checkMedia['post_mime_type'], 'application');

                        if ($checkApp === false) {
                            return $url;
                        } else {
                            $_snx_id = $this->scUpdateAttachUrl($match[3], $match[4]);
                            $rand_string = $this->scRandString(10);
                            // s=site no, id=sc attachment_id, aid=post type attachment type id
                            $param = $rand_string.base64_encode('s='.$match[3].'&id='.$_snx_id.'&a='.$post_id);
                            // return $match[1].'/sc_download.php?s='.$match[3].'&id='.$_snx_id;
                            return $match[1].'/sc_download.php?fid='.$param;
                        }
                    } else {
                        return $url;
                    }
                }

这样重写完, 页面输出的url就已经变掉了.

解下来是sc_download.php文件. 因为我把传入的参数base64加密了一次,并且混入了10个随机的字符,保证url的唯一和随机性. 所以这里要处理下这个逻辑,把原来的参数要处理解析出来.

其他没什么好说的了, 唯一就是header的转发, 在header的Content-Disposition : attachment 无效, WP屏蔽了直接附件的下载. 所以修改下, 修改后完整的header下载是这样

其中$file_path就是实际的文件路径. 需要加上status_header(200)才能保证attachment作为附件的方式下载, 否则就只有浏览器直接打开的inline方式了.

在header发送前就可以作权限处理.模板之类的数据处理. 这里就省略掉了.

@ob_end_clean();
status_header(200);
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);
if (!(isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on" && preg_match("/MSIE/", $_SERVER["HTTP_USER_AGENT"]))) {
    header('Pragma: no-cache');
}
header("Content-Type: application/octet-stream");
header("Content-Type: application/force-download");
header("Content-Type: ".mime_content_type($file_path));
header("Content-Disposition: attachment; filename=\"".urlencode(basename($file_path))."\"");
//header("Content-Disposition: inline; filename=\"".urlencode(basename($file_path))."\";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($file_path));
ob_clean();
flush();
readfile("$file_path");
exit;
时间: 2024-10-09 15:19:13

[自己动手改wordpress.3]给wordpress加上禁止附件外链的相关文章

[自己动手改wordpress.2]给wordpress加上简约的debug sql调试.

还是那个同事. 这次需要帮她打印出sql 输出到页面来调试sql 这里我们用插件或者是配置文件的方式来启动wordpress的debug 在插件目录 wordpress/wp-content/plugins/ 新建一个文件叫bt_debug_sql.php <?php /* Plugin Name:    Bt Debug Sql. Plugin URI:     http://www.btroot.com Description:    Show the sql at the frontpag

How to Get Rid of /wordpress/ From your WordPress Site URL

I brought up a website using wordpress, but I had to visit my website in a way I don't like -- www.example.com/wordpress/. Well, frankly, I want the "/wordpress/" to go to hell. but how? I did a lot of google, and found some pretty well-written

[自己动手改wordpress.1]wordpress的插件User-Access-Manager在新的php版本号里面无法执行的bug.

近期同事在玩wp, 就顺带一起看了下. 她说插件有个不能用. 是一个叫User Access Manager 的插件 详细表现就是在后台填好相应的roles角色的时候, 点提交就会跳到一个错误的页面 看了下解决非常easy, 打开wordpress/wp-content/plugins/user-access-manager/tpl/adminGroup.php 大约在79行有一个reset的输出. 这里实际已经在php5.3的时候,会报Strict Standards: Only variab

[改变自己wordpress.2]至wordpress再加上简单的debug sql调试.

或者说,同事. 需要帮助她打印出来sql 调试输出到页面sql 在这里,我们使用插件或一个的方式来启动配置文件wordpress的debug 在插件文件夹 wordpress/wp-content/plugins/ 新建一个文件叫bt_debug_sql.php <?php /* Plugin Name:    Bt Debug Sql. Plugin URI:     http://www.btroot.com Description:    Show the sql at the front

Web 前端攻防(2014版)

转自:http://fex.baidu.com/blog/2014/06/web-sec-2014/ 禁止一切外链资源 外链会产生站外请求,因此可以被利用实施 CSRF 攻击. 目前国内有大量路由器存在 CSRF 漏洞,其中相当部分用户使用默认的管理账号.通过外链图片,即可发起对路由器 DNS 配置的修改,这将成为国内互联网最大的安全隐患. 案例演示 百度旅游在富文本过滤时,未考虑标签的 style 属性,导致允许用户自定义的 CSS.因此可以插入站外资源: 所有浏览该页面的用户,都能发起任意

WEB前端攻防

1.禁止一切外链资源 外链会产生站外请求,因此可以被利用实施 CSRF 攻击. 目前国内有大量路由器存在 CSRF 漏洞,其中相当部分用户使用默认的管理账号.通过外链图片,即可发起对路由器 DNS 配置的修改,这将成为国内互联网最大的安全隐患. 案例演示 百度旅游在富文本过滤时,未考虑标签的 style 属性,导致允许用户自定义的 CSS.因此可以插入站外资源: 所有浏览该页面的用户,都能发起任意 URL 的请求: 由于站外服务器完全不受控制,攻击者可以控制返回内容: ●如果检测到是管理员,或者

每日晨读_20140924

#技术晨读# HttpOnly隐私嗅探器 cookie的HttpOnly保证了cookie不会被js泄漏,但是总有一些办法绕过httponly,如何来检测我的httponly的cookie是不是泄漏了呢?作者就做了这么个事情… http://drops.wooyun.org/tips/2834 #技术晨读# 为什么mysql的binlog-do-db选项是危险的? 主要是由于跨DB操作的语句是不会被记录到binlog-do-db记录中的,可以使用replicate-wild-*.. http:/

新辰:十种外链终极方法 让SEOer外链之路不再孤独!

大家都知道,外链就是指从别的网站导入到自己网站的链接,导入链接对于新辰网站优化来说是非常重要的一个过程.因此,新辰认为,对于中小型网站来说,外链可是优化的重中之重!因为也有了"外链专员"一说,新辰觉得这只是SEO得最基础的阶段,不是都说SEO的最高境界就是脱离SEO吗?不过,这次还行要说一下外链. 新辰认为,外链不是想象的那样简单,不是简单的在论坛上发个回复,留给签名,因为这基本上算是垃圾外链,也就是行业中所说的:外链工厂.那么我们究竟要怎么去发外链?发外链有没有什么方法?且跟着新辰往

【转载】利用新浪博客建设网站外链

当今网络管理机制越来越完善,允许发外接的网站越来越少,建设网站外链变得越来越困难,今天和大家分享一篇文章,告诉你如何利用新浪博客建设网站外键. 目前国内知名的博客平台有QQ.网易.新浪.搜狐.百度等,先来分析一下为什么我为什么选择新浪博客建设网站外链? 腾讯博客(QQ空间)是不被搜索引擎收录的,所以直接淘汰. 网易博客使用的是三级域名,即使成功建设外链权重也不高. 百度博客(百度HI)即将要关闭了,完全是浪费时间. 搜狐博客也是使用三级域名,权重并不高. 新浪博客的内容页使用 blog.sina