文章列表
 
2009年12月01日 星期二 下午 7:16
作者:老王

欢迎访问我的新主页:http://huoding.com/

程序员们写代码的时候讲究TDD(测试驱动开发):在实现一个功能前,会先写一个测试用例,然后再编写代码使之运行通过。其实当黑客SQL Injection时,同样是一个TDD的过程:他们会先尝试着让程序报错,然后一点一点的修正参数内容,当程序再次运行成功之时,注入也就随之成功了。

进攻:

假设你的程序里有类似下面内容的脚本:

$sql = "SELECT id, title, content FROM articles WHERE id = {$_GET['id']}";

正常访问时其URL如下:

/articles.php?id=123

当黑客想判断是否存在SQL Injection漏洞时,最常用的方式就是在整形ID后面加个单引号:

/articles.php?id=123'

由于我们没有过滤$_GET['id']参数,所以必然会报错,可能会是类似下面的信息:

supplied argument is not a valid MySQL result resource in ...

这些信息就足以说明脚本存在漏洞了,我们可以再耍点手段:

/articles.php?id=0 union select 1,2,3

之所以select 1,2,3是因为union要求两边的字段数一致,前面是id,title,content三个字段,后面1,2,3也是三个,所以不会报语法错误,还有设置id=0是一条不存在的记录,那么查询的结果就是1,2,3,反映到网页上,原本显示id的地方会显示1,显示title的地方会显示2,显示content的地方会显示3。

至于如何继续利用,还要看magic_quotes_gpc的设置:

当magic_quotes_gpc为off时

/articles.php?id=0 union select 1,2,load_file('/etc/passwd')

如此一来,/etc/passwd文件的内容就会显示在原本显示content的地方。

当magic_quotes_gpc为on时

此时如果直接使用load_file('/etc/passwd')就无效了,因为单引号被转义了,但是还有办法:

/articles.php?id=0 union select 1,2,load_file(char(47,101,116,99,47,112,97,115,115,119,100))

其中的数字就是/etc/passwd字符串的ASCII:字符串每个字符循环输出ord(...)

除此以为,还可以使用字符串的十六进制:字符串每个字符循环输出dechex(ord(...))

/articles.php?id=0 union select 1,2,load_file(0x2f6574632f706173737764)

这里仅仅说了数字型参数的几种攻击手段,属于冰山一角,字符串型参数等攻击手段看后面的文档链接。

防守:

网络上有一些类似SQL Injection Firewall的软件可供使用,比如说GreenSQL,如果网站已经开始遭受SQL Injection攻击,那么使用这样的快捷工具往往会救你一命,不过这样的软件在架构上属于一个Proxy的角色,多半会影响网站并发性能,所以在选择与否这个问题上最好视客观条件来慎重决定。很多时候专业的软件并不是必须的,还有很多轻量级解决方案,下面演示一下如何使用awk来检测可能的漏洞。

创建detect_sql_injection.awk脚本,内容如下(如果要拷贝一下内容的话记得不要包括行号):

01 #!/bin/gawk -f
02
03 /\$_(GET|POST|COOKIE|REQUEST)\s*\[/ {
04     IGNORECASE = 1
05     if (match($0, /\$.*(sql|query)/)) {
06         IGNORECASE = 0
07         output()
08         next
09     }
10 }
11
12 function output()
13 {
14     $1 = $1
15     print "CRUD: " $0 "\nFILE: " FILENAME "\nLINE: " FNR "\n"
16 }


此脚本可匹配出类似如下的问题代码,想要扩展匹配模式也容易,只要照猫画虎写if match语句即可。

1:$sql = "SELECT * FROM users WHERE username = '{$_POST['username']}'";
2:$res = mysql_query("SELECT * FROM users WHERE username = '{$_POST['username']}'");


使用前别忘了先chmod +x detect_sql_injection.awk,有两种调用方法:

1:./detect_sql_injection.awk /path/to/php/script/file
2:find /path/to/php/script/directory -name "*.php" | xargs ./detect_sql_injection.awk


会把有问题的代码信息显示出来,样子如下:

CRUD: $sql = "SELECT * FROM users WHERE username = '{$_POST['username']}'";
FILE: /path/to/file.php
LINE: 123


现实环境中有很多应用这个脚本的方法,比如说通过CRON定期扫描程序源文件,或者在SVN提交时通过钩子方法自动匹配。

使用专业工具也好,检测脚本亦罢,都是被动的防守,问题的根本始终取决于在程序员头脑里是否有必要的安全意识,下面是一些必须要牢记的准则:

1:数字型参数使用类似intval,floatval这样的方法强制过滤。
2:字符串型参数使用类似mysql_real_escape_string这样的方法强制过滤,而不是简单的addslashes。
3:最好抛弃mysql_query这样的拼接SQL查询方式,尽可能使用PDO的prepare绑定方式。
4:使用rewrite技术隐藏真实脚本及参数的信息,通过rewrite正则也能过滤可疑的参数。
5:关闭错误提示,不给攻击者提供敏感信息:display_errors=off。
6:以日志的方式记录错误信息:log_errors=on和error_log=filename,定期排查,Web日志最好也查。
7:不要用具有FILE权限的账号(比如root)连接MySQL,这样就屏蔽了load_file等危险函数。
8:......

网站安全其实并不复杂,总结出来就是一句话:过滤输入,转义输出。其中,我们上面一直讨论的SQL Injection问题就属于过滤输入问题,至于转义输出问题,其代表是Cross-site scripting,但它不属于本文的范畴,就不多说了,有兴趣的可以参考XSS (Cross Site Scripting) Cheat Sheet

文档:

addslashes() Versus mysql_real_escape_string()
SQL Injection with MySQL
Advanced SQL Injection with MySQL
MySQL注入中导出字段内容的研究——通过注入导出WebShell
 
2009年11月30日 星期一 下午 8:16
作者:老王

欢迎访问我的新主页:http://huoding.com/

ApacheTop:Top-like display of Apache logs
Iotop:Iotop is a Python program with a top like UI used to show of behalf of which process is the I/O going on.
htop:an interactive process viewer for Linux
memcache-top:perl command-line memcached stat reporter, top-like output
pg_top:pg_top is 'top' for PostgreSQL.
mytop:a top clone for MySQL
innotop:The best top clone for MySQL, with special attention paid to InnoDB
atop:An advanced interactive monitor for Linux-systems to view the load on system-level and process-level.
iftop:display bandwidth usage on an interface
Virt-top:virt-top is a top-like utility for showing stats of virtualized domains.
phptop:php top tool.
 
2009年11月29日 星期日 下午 7:54
作者:老王

欢迎访问我的新主页:http://huoding.com/

服务器时常需要配置无密码的登录方式,最一般的设置方式如下:

# ssh-keygen
# cat ~/.ssh/id_rsa.pub | ssh user@server "cat - >> ~/.ssh/authorized_keys"

其实这方法已经很简单了,不过我总记不清如何正确拼写authorized_keys这个文件名。

还好Linux系统里缺省都包含一个名为ssh-copy-id的工具:

# type ssh-copy-id
ssh-copy-id is /usr/bin/ssh-copy-id

你用cat或者more命令看一下就知道ssh-copy-id本身其实就是一个shell脚本,用法很简单:

# ssh-copy-id -i ~/.ssh/id_rsa.pub user@server

再也不用记如何拼写authorized_keys这个文件名了,是不是很爽,可惜别高兴太早了,ssh-copy-id有一个很要命的问题,那就是缺省它仅仅支持SSH运行在22端口的情况,不过实际上出于安全的需要,我们往往都会更改服务器的SSH端口,比如说改成10022端口,这时候你运行ssh-copy-id就会报错了,直接修改ssh-copy-id脚本当然可以修正这个问题,但是那样显得太生硬了,实际上还有更好的办法:

# vi ~/.ssh/config

加上内容:

Host server
Hostname ip
Port 10022

你也可以单独只加入Port一行配置,那样就是一个全局配置,保存后再运行ssh-copy-id命令就不会报错了。

补充:经网友提示,如果端口不是22,不修改config文件,按如下方式也可以:

ssh-copy-id -i ~/.ssh/id_rsa.pub "-p 10022 user@server"
 
2009年11月26日 星期四 下午 7:58
作者:老王

PHP搭配Memcached已经是妇孺皆知的标配了。再提似乎让人觉得太“圡”了,不过有一些细节不见得人人都清楚,比如说PECL里有两个Memcached的模块,MemcacheMemcached,目前大部分PHP环境里使用的是名字里不带d的Memcache版本,这个版本释出的比较早,是一个原生版本,与之对应的带d的Memcached版本则是建立在libmemcached的基础上,所以说Memcached版本的功能更全一些。

安装Memcached版本的PHP模块

wget http://download.tangent.org/libmemcached-0.35.tar.gz
tar zxf libmemcached-0.35.tar.gz
cd libmemcached-0.35
./configure
make
make install

wget http://pecl.php.net/get/memcached-1.0.0.tgz
tar zxf memcached-1.0.0.tgz
cd memcached-1.0.0
phpize
./configure
make
make install

打开php.ini加上:

extension = "memcached.so"

这样安装就结束了,你可以通过下列命令来确认:

php -m | grep mem

演示Memcached版本的新功能

先虚构一个问题,假设counter初始值是一个整数,不使用increment方法,通过get/set完成每次加一。

在Memcache版本里,我们只能按照大致如下的方式来进行:

$m = new Memcache();
$m->addServer('localhost', 11211);
$v = $m->get('counter');
$m->set('counter', $v + 1);

由于get/set这两个动作无法作为一个原子来操作,所以当多个进程同时处理时,会出现丢失的可能,更让人恼火的是,你根本就不知道什么时候出现丢失。

再看看Memcached版本里,我们是如何做的:

$md = new Memcached();
$md->addServer('localhost', 11211);
$v = $md->get('counter', null, $token)
$md->cas($token, 'counter', $v + 1);

cas是Memcached版本里提供的功能,说白了就是一个乐观锁的功能,如果你把$token的值var_dump出来,就会发现$token其实就是一个版本号,如果通过get得到的$token版本号在cas的时候不对应,就说明已经有别的操作更新了,此时cas操作会失败,至于如何继续操作,就看你自己了。

注:如果你想手动重现一下冲突的情况,可在get和cas之间sleep若干秒,并拷贝两份脚本,先后执行。

顺便说一句,推荐的Memcached版本模块的哈希设置如下:

$md->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$md->setOption(Memcached::OPT_HASH, Memcached::HASH_CRC);

总结

Memcached版本还有很多Memcache没有的功能,比如通过getByKey, setByKey等自动支持多个服务器,就不赘述了,该用哪个扩展已经不言自明了。

补充:http://code.google.com/p/memcached/wiki/PHPClientComparison
 
2009年11月25日 星期三 下午 4:41
作者:老王

欢迎访问我的新主页:http://huoding.com/

配置主从服务器

Redis主从服务器的搭建很简单,只要少许配置即可,为了演示的方便,我们就在一台服务器上配置:

前提是你已经有了一台Redis服务器,如果没有可以参考我以前的文章安装。下面看看如何配置从服务器:

假设主服务器的配置文件是:/etc/redis.conf,我们复制一份作为从服务器的配置文件:

cp /etc/redis.conf /etc/redis_slave.conf

并作修改:

# vi /etc/redis_slave.conf
port 6380
dbfilename dump_slave.rdb
slaveof 127.0.0.1 6379

主服务器的端口使用的是缺省的6379,从服务器的端口我们设置成6380。

然后插入一些测试数据:

redis-benchmark

由于我们没有设定任何参数,所以使用的是缺省端口(6379),在本例中就是主服务器。

然后启动从服务器:

redis-server /etc/redis_slave.conf

确认一下是否都正常启动了:

ps -ef | grep redis

进入数据目录,查一下数据文件的散列:

md5sum *.rdb

你会发现数据文件散列都一样,自动同步了。

然后我们关闭一下从服务器(不关也行,我就是为了告诉你如何正确关闭redis服务器):

redis-cli -p 6380 shutdown

接着再往主服务器上写入测试数据:

redis-benchmark -l

这会循环插入测试数据,数据量的大小取决于时间的长短,你可以在适当的时候按ctrl+c停止。

如果从服务器没有启动的话,接着再重新启动从服务器:

redis-server /etc/redis_slave.conf

通过观察文件大小你会发现数据会自动同步,如果没有重启动从服务器,那么数据文件的md5sum散列值可能不同,这是正常的,不要紧。

在操作过程中,有时候你会发现主从服务器的数据文件大小不一样,一般来说也不是问题,因为redis是异步写入磁盘的,此时可能有部分数据还在内存中,没有同步到磁盘,所以文件大小略显不同,可以分别在主从服务器上执行:

redis-cli save(redis-cli -p 6380 save)

这条命令强制同步到磁盘,再看大小就应该一样了。

配置文件redis.conf里有一部分和save相关的参数,缺省如下:

# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
save 900 1
save 300 10
save 60 10000

在主服务器上,我们可以去掉上面的设置,改成类似下面的设置(只要参数值够大即可):

save 10000000000 10000000000

如此一来主服务器变成一个完全的内存服务器,所有的操作都在内存里完成,“永远”不会再往磁盘上持久化保存数据,异步的也没有。持久化则通过从服务器来完成,这样在操作主服务器的时候效率会更高。不过要注意的一点是此方法不适合保存关键数据,否则一旦主服务器挂掉,如果你头脑一热简单的重启服务,那么从服务器的数据也会跟着消失,此时,必须拷贝一份备份数据到主服务器,然后再重启服务才可以,数据的恢复稍显麻烦。

从服务器也可以通过设置这个参数来调整从内存同步到磁盘的频率。

利用主从服务器备份

可以利用主从服务器的方便性来备份,专门做一台从服务器用于备份功能,当需要备份的时候,在从服务器上执行下列命令:

redis-cli save
redis-cli shutdown

然后拷贝数据目录下的rdb文件即可。

另:官方文档介绍不使用主从,直接在服务器上cp就可以,不过感觉利用从服务器备份对线上服务器影响更小些。

总结

如果你以前做过MySQL主从服务器的话,两相对比,你会发现Redis主从服务器不用做前期的数据同步,设置好了从服务器,简单启动就OK了。至于Redis主从怎么用,是备份也好,读写分离也好,就看你的想象力了。

资料

官方文档
 
   
 
 
文章存档
 
     
 
最新文章评论
  

[表情]
 

不错!
 

linux大师之路,www.linuxmr.com
 

引导一直没有整明白说。
 

[表情]
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu