深入escape_string for Mysql(转)

继续发老文档,这片是我09年分析PHP宽字节绕过addslashes等常用SQL注入过滤语句时写的分析文档。刚才发struts那篇文档,这篇一并发上来,充实一下PHP区的内容。
同样请各位注意,这篇文档的内容是09年的,当时甚至还有用PHP4的,可能有部分过时,查看时请注意分辨。

Author: wofeiwo#80sec.com
Date: 2009-7-28

一、背景
在日常的web程序编写过程中,经常需要处理字符集的问题。在PHP+Mysql的环境下,通常会使用SET NAMES来设定Mysql数据库当前所使用的字符集。
但是这就很容易造成一个问题:通过字符集转换进行注射攻击,通常这样的代码能够绕过PHP 函数addslashes或mysql_escape_string的过滤。因为addslashes、mysql_escape_string没有考虑到宽字符,一旦使用就会造成误过滤。能够被巧妙运用并造成SQL注射攻击。
因此,为了更好的安全性需要,我们需要使用脚本语言提供的real_escape_string API对用户输入到Mysql数据库中执行的语句或变量进行安全性的过滤。这是一个非常良好的习惯。Real_escape_string函数理论上会按照当前字符集的设置,有效处理单、宽字符,不放过任何一个敏感字符(如单引号,双引号,反斜线等)。但是,在现实的运用过程中,却常常并非如此。
让我们来看看如下一份代码。
<?php
$mysqli = new mysqli("localhost", "user", "pass", "test", 3306);

/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

$mysqli->query('SET NAMES gbk'); //使用set names设置字符集
$city = chr(0xbf).chr(0x5c); //0xbf5c是个有效的gbk字符,模拟用户输入
$city = $mysqli->real_escape_string ($city);//使用real_escape进行过滤

/* this query will fail, cause we didn't escape $city */
if (!$mysqli->query("INSERT into myCity(name) VALUES ('$city')")) {
print "INSERT into myCity (name) VALUES ('$city')\n";
printf("Error: %s\n", $mysqli->error);
}

var_dump($city);

var_dump($mysqli->client_encoding());

$mysqli->close();
?>

这份代码的结构很常见,它的执行结果如下:
INSERT into myCity (name) VALUES ('縗\')
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''縗\')' at line 1
string(3) "縗\"
string(6) "latin1"

我们可以看到,虽然使用SET NAMES设置了数据库的字符集,但是real_escape_string函数却没有起到他该有的作用:按照当前字符集,过滤敏感字符。反而还是和addslashes一样,一个字节一个字节过滤,使得“0xbf5c”(縗)被过滤成了“0xbf5c5c”(縗\)。从而转义了后面的单引号,破坏了sql语句。

二、原因
再仔细查看上面一段程序的结果。会发现mysql的client_encoding()函数返回的字符集是”latin1”。很明显,real_escape_string是按照了这个字符集对参数进行过滤,所以才导致了以上的这些问题。
在mysql手册中,对set names的作用如下表示:
SET NAMES 'x'语句与这三个语句等价:

mysql> SET character_set_client = x;
mysql> SET character_set_results = x;
mysql> SET character_set_connection = x;

看上去完美无缺,所有mysql涉及到的三种字符集都被修改了(客户端字符集,连接字符集,输出结果字符集)。但是为何还是不起作用?
通过跟踪PHP中mysql_real_escape_string的代码,发现他是直接调用了mysql提供的mysql_real_escape_string这个API:
ulong STDCALL
mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
ulong length)
{
if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
return (uint) escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
return (uint) escape_string_for_mysql(mysql->charset, to, 0, from, length); //这里使用的是struct mysql中的charset成员来作为判断当前字符集的
}

从代码中可以看到它是通过mysql这个结构体中charset来判断当前使用的字符集设置的。而使用sql语句对mysql环境变量进行设置,无论如何设置,都无法改变客户端内存中mysql结构体的charset成员的值。

三、解决方案
因此,为了修改此结构体,从而使mysql_real_escape_string函数真正生效,只有通过PHP提供的mysql_set_charset或mysqli_set_charset函数来进行:
int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name)
{
struct charset_info_st *cs;
const char *save_csdir= charsets_dir;

if (mysql->options.charset_dir)
charsets_dir= mysql->options.charset_dir;

if (strlen(cs_name) < MY_CS_NAME_SIZE &&
(cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(0))))
{
char buff[MY_CS_NAME_SIZE + 10];
charsets_dir= save_csdir;
/* Skip execution of "SET NAMES" for pre-4.1 servers */
if (mysql_get_server_version(mysql) < 40100)
return 0;
sprintf(buff, "SET NAMES %s", cs_name); //set names语句
if (!mysql_real_query(mysql, buff, (uint) strlen(buff)))
{
mysql->charset= cs; //修改了结构体的charset
}
}

从代码中可以看出,这两个函数不仅仅执行了SET NAMES SQL语句,还增加了关键的一步:“mysql->charset= cs;”。 修改了mysql结构体中的charset为当前字符集,这样才能使mysql_real_escape_string真正起作用。
我们再来测试一下,修改上文测试代码中的:
$mysqli->query('SET NAMES gbk');
修改为:
$mysqli->set_charset('gbk');
然后再看看执行结果:
string(2) "縗"
string(3) "gbk"

成功执行,而client_encoding也变成了gbk,说明我们的mysql_real_escape_string函数真正按照预期起作用了。

四、扩展
通过以上方法的确可以解决此字符集转换注入问题,但是,mysqli_set_charset函数对PHP和Mysql有版本要求,必须当mysql版本大于5,PHP版本大于5.0.5时,此函数才有效。至于另一个mysql_set_charset函数,则更要求PHP版本大于5.2.3时才能有效。这对于某些环境下的老版本应用程序可能并不适用。
这里提供另一个解决方案,可以在Mysql 4.1以上版本有效(注:这也是Discuz当年用的方法)。使用:
SET character_set_client=binary;
当使用此语句时,将客户端连接设置为binary,则无论用户输入中是否有敏感字符,mysql在解析时,都只会将其当做二进制数据,而不会当做转义字符或特殊字符处理,因此不会破坏sql语句。应用程序只需要在从mysql数据库取出数据后,在输出时按照当前字符集输出即可。

我们再来测试一下,修改上文测试代码中的:
$mysqli->query('SET NAMES gbk');
修改为:
$mysqli->query('SET character_set_client=binary;');
然后再看看执行结果:
string(3) "縗\"
string(6) "latin1"

可见虽然反斜杠被输入进了sql语句,但是并没有被当做转义字符处理。无法破坏sql语句结构,可以安全运行。
然而此方法也有另外的问题,比如上例中插入的数据就比正常插入的数据多出了一个反引号。而当使用like语句时,类似” like ‘%$_GET[‘user_input’]%’ ”这样的语句,如果当时的显示是gbk编码,而用户输入的是一个单字符,而此单字符又是gbk双字符编码中的一部分时,则此语句可能会匹配到非预期的内容。此外,使用binary还可能会造成其他不可知的问题。

五、总结
希望各位在编写程序时,按照当时环境和需求,选择以上两种方式中的一种方式进行字符集数据库操作。通常情况下,我推荐使用mysql_set_charset设置字符集的方案,只有在环境不允许的情况下,才推荐使用第二种binary编码的方案。但是无论在什么情况下,都禁止使用”SET NAMES”来作为设置字符集的操作,因为从安全上说,这一设置根本没有起到任何作用。

发表在 php, 涨姿势 | 标签为 , | 留下评论

找不到原因的一段解析错误

以下是百度搜到的,主要是由于自己不知道如果php5高版本5.3以上默认关闭了段标记,还有自己不知道硬使用短标记会引起解析错误

Parse error: syntax error, unexpected $end in *****.php on line 330 

In PHP 5, the following error may appears as an error entry in Apache error log or simply displays on PHP web page, even if calling to php scripts with php_info() works perfectly and successfully returns information on PHP configurations:

Parse Error: syntax error, unexpected $end in ….. scripts.php on line …

The error may caused by a missing curly bracket in PHP script coding. Beside, it may also caused by error in PHP coding in class definition, as in PHP, a class definition cannot be broke up and distributed into multiple files, or into multiple PHP blocks, unless the break is within a method declaration.

But more commonly, the error is often caused by the use of Short Open tags in PHP,

To use short open tags, it must be enabled in PHP.INI. Search for short_open_tag in PHP.INI, and change the value to On. The line should look line:

short_open_tag = On

发表在 涨姿势 | 留下评论

php cli模式查看模块安装信息

~/datacenter/php/bin/php –ri imagick

–ri <name>      Show configuration for extension <name>.

imagick

imagick module => enabled
imagick module version => 3.0.1
imagick classes => Imagick, ImagickDraw, ImagickPixel, ImagickPixelIterator
ImageMagick version => ImageMagick 6.6.7-10 2012-08-30 Q16 http://www.imagemagick.org
ImageMagick copyright => Copyright (C) 1999-2011 ImageMagick Studio LLC
ImageMagick release date => 2012-08-30
ImageMagick number of supported formats: => 183
ImageMagick supported formats => 3FR, A, AAI, AI, ART, ARW, AVI, AVS, B, BMP, BMP2, BMP3, C, CAL, CALS, CANVAS, CAPTION, CIN, CIP, CLIP, CMYK, CMYKA, CR2, CRW, CUR, CUT, DCM, DCR, DCX, DDS, DFONT, DNG, DPS, DPX, EPDF, EPI, EPS, EPS2, EPS3, EPSF, EPSI, ERF, FAX, FITS, FRACTAL, FTS, G, G3, GIF, GIF87, GRADIENT, GRAY, HISTOGRAM, HRZ, HTM, HTML, ICB, ICO, ICON, INFO, INLINE, IPL, JNG, JPEG, JPG, K, K25, KDC, LABEL, M, M2V, M4V, MAC, MAP, MAT, MATTE, MIFF, MNG, MONO, MOV, MP4, MPC, MPEG, MPG, MRW, MSL, MSVG, MTV, MVG, NEF, NULL, O, ORF, OTB, OTF, PAL, PALM, PAM, PATTERN, PBM, PCD, PCDS, PCL, PCT, PCX, PDB, PDF, PDFA, PEF, PES, PFA, PFB, PFM, PGM, PICON, PICT, PIX, PJPEG, PLASMA, PNG, PNG24, PNG32, PNG8, PNM, PPM, PREVIEW, PS, PS2, PS3, PSB, PSD, PWP, R, RADIAL-GRADIENT, RAF, RAS, RGB, RGBA, RGBO, RLA, RLE, SCR, SCT, SFW, SGI, SHTML, SR2, SRF, STEGANO, SUN, SVG, SVGZ, TEXT, TGA, THUMBNAIL, TILE, TIM, TTC, TTF, TXT, UIL, UYVY, VDA, VICAR, VID, VIFF, VST, WBMP, WMV, WPG, X, X3F, XBM, XC, XCF, XPM, XPS, XV, XWD, Y, YCbCr, YCbCrA, YUV

Directive => Local Value => Master Value
imagick.locale_fix => 0 => 0
imagick.progress_monitor => 0 => 0

发表在 涨姿势 | 留下评论

SecureCRT超时自动断开的问题

解决办法:

Options->Session Options->Terminal->Anti-idle->勾选Send protocol NO-OP

 

不知道管不管用,今天晚上不关电脑,明天早上看看,睡觉~~~

发表在 小工具 | 留下评论

诡异的No recipient addresses found in header

改完一些代码,php脚本运行时突然报No recipient addresses found in header错误,看php的error_log和自己的日志也没内容,网上搜了一圈,说是跟mail有关,但是我也没用mail啊,最后还是一行一行的试吧。后来才发现是这个搞的鬼。

/**
 * @param array
 * @return void
 */
function LOG_DEBUG(){
 $args = func_get_args();
 $format = array_shift( $args );
 error_log( 'DEBUG: ' . $format, $args );
}

这一行我其实忘写了点东西,

/**
* @param array
* @return void
*/
function LOG_DEBUG(){
$args = func_get_args();
$format = array_shift( $args );
error_log( vsprintf(‘DEBUG: ‘ . $format, $args) );
}

error_log搞的鬼~~~


							
发表在 php异常 | 留下评论

php的两种post方式

<?php
$scheme = 'http';
$host = '127.0.0.1';
$path = '/oauth2/token.php';
$data = array('grant_type' => 'client_credentials');
$user_pass = 'testclient:testpass';
if( get_extension_funcs('curl') ){
 $ch = curl_init();
 curl_setopt($ch, CURLOPT_URL, $scheme . '://' . $host . $path);
 curl_setopt($ch, CURLOPT_POST, 1);
 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
 curl_setopt($ch, CURLOPT_USERPWD, $user_pass);
 curl_exec($ch);
 curl_close($ch);
}else{
 $data_str = http_build_query($data);
 $user_pass_encode = base64_encode($user_pass);
 $fp = @fsockopen($host, 80, $errno, $errstr, 30);
$header = "POST $path HTTP/1.1\r\n";
 $header .= "Host: 127.0.0.1\r\n";
 $header .= "Accept: */*\r\n";
 $header .= "Content-length: " . strlen($data_str) . "\r\n";
 $header .= "Authorization: Basic $user_pass_encode\r\n";
 $header .= "Content-type: application/x-www-form-urlencoded\r\n";
 $header .= "Connection: Close\r\n\r\n";
 $header .= $data_str . '\r\n\r\n';
if ($fp)
 {
 $line = '';
 fwrite($fp, $header);
 while (!feof($fp)) 
 {
 $line .= fgets($fp, 256);
 }
 fclose($fp);
echo $line;
 }
 else
 {
 echo $errno, " ", $errstr;
 }
}
发表在 php | 留下评论

OAuth2.0简单介绍

它主要的目的如下:
如果用户的照片在A网站,他想要在B网站使用A网站的头像,并不需要向B网站提供自己在A网站的用户名和密码,而直接给B一个Access Token来获取A站的照片
具体流程如下:
1)用户访问网站B
2)B需要验证用户的身份
3)B将用户定向到A网站,用户输入帐号密码登录A网站
4)A网站询问是否要将Authentication的权利给B网站
5)用户告诉A站可以将认证权给B站
6)A网站把Authorization Code发给B站
7)B站用Autorization Code向A站换取Access Token
8)当B站拥有Access Token时,就拥有了用户在A站的一些访问权限
这是典型的Authorization Code Grant,常常运用于网络应用之中

参考:http://www.rollosay.com/it/%E4%BD%BF%E7%94%A8OAuth-Server-PHP%E5%AE%9E%E7%8E%B0OAuth2%E6%9C%8D%E5%8A%A1

发表在 概念名词 | 留下评论

php查看端口是否被占用

<?php
$fp = @fsockopen("127.0.0.1", 80, $errno, $errstr, 1);
 $out = "GET / HTTP/1.1\r\n";
 $out .= "Host: 127.0.0.1\r\n";
 $out .= "Connection: Close\r\n\r\n";
if ($fp)
{
 echo 'Your port 80 is actually used by :
';
 fwrite($fp, $out);
 while (!feof($fp)) 
 {
 $line = fgets($fp, 128);
 if (ereg('Server: ',$line))
 {
 
 echo $line;
 $gotInfo = 1;
 } 
 
 }
 fclose($fp);
 if ($gotInfo != 1)
 echo 'Information not available (might be Skype).';
}
else
{
 echo 'Your port 80 is not actually used.';
}
发表在 socket | 留下评论

查看php扩展.so文件里面的内容

今天一个大神问我公司ral版本号是哪个,瞬间懵逼,我tm哪儿知道哇,于是get到了一下命令

[rd@localhost php]$ strings ./lib/php/extensions/no-debug-non-zts-20060613/ral.so |grep -P “\d+\.\d+\.\d+\.\d+”
2.0.13.8
RAL 2.0.13.8 didn’t support convert overwrite at runtime, service=%s
RAL 2.0.13.8 didn’t support protocol overwrite at runtime, service=%s
RAL/2.0.13.8 (internal request)
1.0.0.0
127.0.0.1
0.0.0.1
127.0.0.1
127.0.0.1;192.168.1.*
255.255.255.255
1.2.1.2
1.2.0.4

 

get到了grep 加 -P时可以用prel风格的正则

  -P, –perl-regexp         PATTERN is a Perl regular expression

另外,strings的用法之前没用过,其实strings跟cat没啥区别,只不过cat只能是查看普通文本,strings可以查看二进制的文本甚至更多。

strings: supported targets: elf64-x86-64 elf32-i386 a.out-i386-linux efi-app-ia32 elf64-little elf64-big elf32-little elf32-big srec symbolsrec tekhex binary ihex

发表在 bash | 留下评论

REST 是个啥

rest的6个优点:

1.客户-服务器(Client-Server)客户端服务器分离

优点,提高用户界面的便携性(操作简单)

通过简化服务器提高可伸缩性(高性能,低成本)

允许组件分别优化(可以让服务端和客户端分别进行改进和优化)

2.无状态(Stateless)

从客户端的每个请求要包含服务器所需要的所有信息

优点:

提高可见性(可以单独考虑每个请求)

提高了可靠性(更容易从局部故障中修复)

提高可扩展性(降低了服务器资源使用)

3.缓存(Cachable)

服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的信息发送请求。

优点:

减少交互次数

减少交互的平均延迟

4.分层系统(Layered System)

系统组件不需要知道与他交流组件之外的事情。封装服务,引入中间层。

优点:

限制了系统的复杂性

提高可扩展性

5.统一接口(Uniform Interface)

优点:

提高交互的可见性

鼓励单独改善组件

6.支持按需代码(Code-On-Demand 可选)

优点:

提高可扩展性

发表在 概念名词 | 留下评论