CLIENT_MULTI_RESULTS

本文深入探讨了PHP MySQL环境下多语句执行的常见误解,揭示了其背后的技术原理及实现方法,包括如何绕过PHP自身的限制,利用PDO支持多语句执行,重新激活MySQL的多语句能力,以及如何在实际应用中灵活运用这一特性,挑战传统观念,解锁SQL注入的新可能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其中的一个参数CLIENT_MULTI_RESULTS不明白是什么意思,google之,在mysql的官方主页上关于mysql提供的c接口的文档(http://dev.mysql.com/doc/refman/5.0/en/mysql-real-connect.html)里找到了这个参数和其他一些参数,我大概翻译了一下描述,如下:
Flag NameFlagDescription
CLIENT_COMPRESSUse compression protocol.(使用压缩协议。)
CLIENT_FOUND_ROWSReturn the number of found (matched) rows, not the number ofchanged rows.(返回找到(匹配)的行数,而不是改变了的行数。)
CLIENT_IGNORE_SIGPIPEPrevents the client library from installing a SIGPIPE signal handler. This can be used to avoidconflicts with a handler that the application has alreadyinstalled.(阻止客户端库安装一个SIGPIPE信号处理器。这个可以用于当应用程序已经安装该处理器的时候避免与其发生冲突。
CLIENT_IGNORE_SPACEAllow spaces after function names. Makes all functions namesreserved words.(允许在函数名后使用空格。所有函数名可以预留字。)
CLIENT_INTERACTIVEAllow interactive_timeout seconds(instead of wait_timeout seconds) ofinactivity before closing the connection. The client's sessionwait_timeout variable is set tothe value of the session interactive_timeoutvariable.(允许使用关闭连接之前的不活动交互超时的描述,而不是等待超时秒数。客户端的会话等待超时变量变为交互超时变量。)
CLIENT_LOCAL_FILESEnable LOAD DATA LOCAL handling.
CLIENT_MULTI_RESULTSTell the server that the client can handle multiple result setsfrom multiple-statement executions or stored procedures. This flagis automatically enabled ifCLIENT_MULTI_STATEMENTS is enabled. See the notefollowing this table for more information about thisflag.(通知服务器客户端可以处理由多语句或者存储过程执行生成的多结果集。当打开CLIENT_MULTI_STATEMENTS时,这个标志自动的被打开。可以在本表后查看更多关于该标志位的信息。
CLIENT_MULTI_STATEMENTSTell the server that the client may send multiple statements ina single string (separated by “;”). If this flag is not set,multiple-statement execution is disabled. See the note followingthis table for more information about this flag.(通知服务器客户端可以发送多条语句(由分号分隔)。如果该标志为没有被设置,多条语句执行。)
CLIENT_NO_SCHEMADon't allow the db_name.tbl_name.col_name syntax.This is for ODBC. It causes the parser to generate an error if youuse that syntax, which is useful for trapping bugs in some ODBCprograms.(不允许“数据库名.表名.列名”这样的语法。这是对于ODBC的设置。当使用这样的语法时解析器会产生一个错误,这对于一些ODBC的程序限制bug来说是有用的。
CLIENT_ODBCUnused.(不使用)
CLIENT_SSLUse SSL (encrypted protocol). This option should not be set byapplication programs; it is set internally in the client library.Instead, usemysql_ssl_set() before calling mysql_real_connect().(使用SSL。这个设置不应该被应用程序设置,他应该是在客户端库内部是设置的。可以在调用mysql_real_connect()之前调用mysql_ssl_set()来代替设置。
CLIENT_REMEMBER_OPTIONSRemember options specified by calls to mysql_options(). Without this option, ifmysql_real_connect() fails, you must repeatthemysql_options() calls before trying to connectagain. With this option, themysql_options() calls need not berepeated.(记住通过调用mysql_options()生成的设置。如果不使用这个设置,当mysql_real_connect失败时,再重新连接之前必须反复调用mysql_options()。当然,如果使用这个设置,就不必反复调用了。

   下面有对于 CLIENT_MULTI_STATEMENTS的说明:
If you enable CLIENT_MULTI_STATEMENTSor CLIENT_MULTI_RESULTS, you shouldprocess the result for every call to mysql_query() or mysql_real_query() by using a loop that calls mysql_next_result() to determine whether there are moreresults. For an example, see Section 20.9.12, “C API Support for MultipleStatement Execution”.

如果打开了 CLIENT_MULTI_STATEMENTS或CLIENT_MULTI_RESULTS,你必须对每一个mysql_query()或者mysql_real_query()的调用结果通过一个循环来处理,在这个循环中,调用mysql_next_result()来决定(发现)是否有更多的结果,如Section 20.9.12,“C API Support for Multiple Statement Execution”


 PHP+MySQL多语句执行
一直以来PHP+MySQL环境下,无论是写程序或者是注入攻击,是无法多语句执行的,这么广为人知的常识,没理由会有人不知道。可权威就是用来被挑战的,常识也就是为了被打破的。如果没有一点创新性,追根到底的求知欲,一直在条条框框里挣扎,那还有什么争取自由、解析世界的Hacker,Geek精神?
最近看到一个很简单的sql注入案例,虽然漏洞很简单,但是其中蕴含的内容可大大不同。
亮点在于:作者居然在注射利用过程中使用了mysql的多语句执行。
感谢作者@紫梦芊 ,让我们一直以来奉为金科玉律的信条被彻底颠覆。
本着Know it then hack it的想法,我仔细研究了一下这一条“常识”是如何形成,又如何被作者打破的。
从最早国内angel介绍的《SQL Injection with MySQL》&《Advanced SQL Injection with MySQL》这两篇文章开始,PHP+MySQL注入就一直停留在UNION Select的基石上建立起来的。可Union select作为SQL Inj的方法,一直都有很多限制,比如需要猜字段数、猜表名,非select语句就无法使用union,注入点在order by或者group by语句后就很难进行操作,盲注比较复杂等等问题。
可为什么非要使用union select?除了当年MySQL 3.x不支持子查询查询数据之外,主要原因,在实践中发现注射中同样不能像mssql一样用分号来分割执行多个sql语句。为什么?因为PHP的MySQL,MySQLi扩展,都因为安全原因,在connect的时候,都不允许设置CLIENT_MULTI_STATEMENTS,哪怕你手工在connect的时候设置了这个flag,php在源代码级别,仍然会帮你去除掉:
 php-5.3.8/ext/mysqli/mysqli_nonapi.c
/* set some required options */
flags |= CLIENT_MULTI_RESULTS; /* needed for mysql_multi_query() */
/* remove some insecure options */
flags &= ~CLIENT_MULTI_STATEMENTS;   /* don't allow multi_queries via connect parameter */
而没有这个特殊的设置,MySQL是不允许在一个mysql_query中使用分号执行多语句的。于是,这也就是长久以来,大家都认为mysql是不允许多语句执行的根本原因。实际上这恰恰是个错误的认识,实际上mysql早在4.1版本就允许多语句执行。只是PHP自身限制了这种用法。
 而是不是PHP就完全无法使用多语句执行呢?答案是否,利用mysqli_multi_query就可以执行多语句,但是现实应用中基本没有人会用这个语句。而另一个常被程序员所使用的PDO方式操作数据库,情况又如何?
create table `car`(`name` varchar(32), `type` varchar(32));
    <?php
    $db = new PDO("mysql:host=localhost:3306;dbname=test", 'root', '');
    // works regardless of statements emulation
    // $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
    $sql = "DELETE FROM car; INSERT INTO car(name, type) VALUES ('car13', 'coupe'); INSERT INTO  car(name, type) VALUES ('car2', 'coupe');";
     try {
        //$db->exec($sql);
      $db->query($sql);
      //$stmt = $db->prepare($sql);
      //$stmt->execute();
    }
    catch(PDOException $e){
        echo $e->getMessage();
        die();
    }

    测试了一下,果然,以上这些多语句,在PDO的情况下,安然执行了。果断查找PHP源代码:
php-5.3.8/ext/mysqlnd/mysqlnd_enum_n_def.h
#define CLIENT_MULTI_STATEMENTS    (1UL << 16) /* Enable/disable multi-stmt support */
php-5.3.8/ext/pdo_mysql/mysql_driver.c
#ifdef CLIENT_MULTI_STATEMENTS |CLIENT_MULTI_STATEMENTS
果然,PHP源代码默认支持了多语句执行特性,已经在mysqlnd这个driver中定义了多语句执行的参数。
要知道,现在多数PHP的编程框架为了支持多数据库类型,基本都会使用PDO作为底层数据库连接方式。这意味着什么?意味着在PDO的环境中,注入点是什么类型(insert,update,delete,select)已经不重要了,注入点在语句的什么位置也不重要了(table,where,orderby),一切可能性都重现了,mysql功能突然全开放在我们面前,都可以利用多语句的方式,重新构造我们所需要的sql语句。
那是否利用PDO使用这个就完全没有限制了呢?当然也不是。首先,一般框架使用PDO作为数据库连接,常用的数据库操作,都是使用参数绑定或者prepare的方式,进行参数绑定的。而在默认情况下,这种参数绑定是在php客户端进行的,在这种情况下,是不允许多语句的。只有如下方法:
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
才可以利用prepare执行多语句(因为参数绑定是在server端执行),而这种状况非常少见。
所以,真正可以利用的多语句注射,只能是存在于利用PDO连接数据库,并直接使用exec或者query函数进行执行sql语句的地方。是不是太苛刻?其实这种例子并不少见,各位只要用心找,总是能找到的。
文末再次感谢引导我寻找原因的漏洞作者@紫梦芊 ,以及@Laruence ,给我看PHP代码提供了不少帮助
作者 GARY

现在的代码在结果中会出现一个level_1, 怎么去掉:client_id level_1 date pos_sim from sklearn.metrics.pairwise import cosine_similarity # 计算所有股票的持仓相似度 def calculate_daily_positions_consine_similarity(df): df = df.copy() all_dates = pd.date_range(start=df['date'].min(), end=df['date'].max(), name='date').strftime("%Y-%m-%d") all_clients = df['client_id'].unique() all_instruments = df['instrument_id'].unique() full_panel = pd.MultiIndex.from_product( [all_dates, all_clients, all_instruments], names=['date', 'client_id', 'instrument_id'] ).to_frame(index=False) merged = full_panel.merge( df, on=['date', 'client_id', 'instrument_id'], how='left' ) # 填充缺失日期 merged['cum_qty'] = merged.groupby(['client_id', 'instrument_id'])['cum_qty'].ffill() merged = merged.fillna(0) merged['position_type'] = np.where( merged['cum_qty'] > 0, 'long', np.where(merged['cum_qty'] < 0, 'short', 'neutral') ) # 计算每日持仓统计 merged['is_long'] = (merged['position_type'] == 'long') merged['is_short'] = (merged['position_type'] == 'short') daily_stats = merged.groupby(['client_id', 'date']).agg( long_qty=('cum_qty', lambda s: s[s > 0].sum()), short_qty=('cum_qty', lambda s: abs(s[s < 0].sum())) ).reset_index() daily_stats["total_qty"] = daily_stats["long_qty"] + daily_stats["short_qty"] merged = merged.merge(daily_stats, on=['client_id', 'date']) merged["posion_weight"] = merged["cum_qty"] / merged["total_qty"] merged["posion_weight"] = merged["posion_weight"].fillna(0) # 计算持仓余弦相似度 def calculate_cosine_similarity(client_group): client_group = client_group.sort_values('date') stock_matrix = client_group.pivot(index='date', columns='instrument_id', values='posion_weight').fillna(0) similarity_matrix = cosine_similarity(stock_matrix.values) results = [] # 计算每日相似度(与前一日比较) for i in range(1, len(stock_matrix)): results.append({ 'date': stock_matrix.index[i], 'pos_sim': similarity_matrix[i, i-1] # 取对角线下方相邻元素 }) # # 计算每日相似度(与前一日比较) # for i in range(1, len(stock_matrix)): # vec_today = stock_matrix.iloc[i].values.reshape(1, -1) # vec_yesterday = stock_matrix.iloc[i-1].values.reshape(1, -1) # sim = cosine_similarity(vec_today, vec_yesterday)[0][0] # results.append({'date': stock_matrix.index[i], 'pos_sim': sim}) return pd.DataFrame(results) # 每个client,每日,根据所有股票仓位,可以计算相似性 similarity_results = merged.groupby(['client_id']).apply(calculate_cosine_similarity).reset_index() return similarity_results # 计算每日持仓余弦相似性 daily_positions_cos = calculate_daily_positions_consine_similarity(multi_cpty_df[(multi_cpty_df["client_id"] == "ABRH400")]) daily_positions_cos
07-30
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[146], line 76 73 return all_client_df 75 # 计算每日持仓余弦相似性 ---> 76 daily_positions_cos = calculate_positions_consine_similarity(multi_cpty_df[(multi_cpty_df["client_id"] == "ABRH400")], interval=90) 78 # plot_pos_sim(daily_positions_cos["date"], daily_positions_cos["pos_sim"]) 80 daily_positions_cos Cell In[146], line 68, in calculate_positions_consine_similarity(df, interval) 66 similarity_median = calculate_cosine_similarity(merged, interval) 67 print(similarity_median) ---> 68 similarity_results = pd.DataFrame({"client_id": client, "pos_sim": similarity_median}) 70 all_client_result.append(similarity_results) 72 all_client_df = pd.concat(all_client_result, axis=1) File c:\Users\matianht\.conda\envs\nomura\lib\site-packages\pandas\core\frame.py:778, in DataFrame.__init__(self, data, index, columns, dtype, copy) 772 mgr = self._init_mgr( 773 data, axes={"index": index, "columns": columns}, dtype=dtype, copy=copy 774 ) 776 elif isinstance(data, dict): 777 # GH#38939 de facto copy defaults to False only in non-dict cases --> 778 mgr = dict_to_mgr(data, index, columns, dtype=dtype, copy=copy, typ=manager) 779 elif isinstance(data, ma.MaskedArray): 780 from numpy.ma import mrecords File c:\Users\matianht\.conda\envs\nomura\lib\site-packages\pandas\core\internals\construction.py:503, in dict_to_mgr(data, index, columns, dtype, typ, copy) 499 else: 500 # dtype check to exclude e.g. range objects, scalars 501 arrays = [x.copy() if hasattr(x, "dtype") else x for x in arrays] --> 503 return arrays_to_mgr(arrays, columns, index, dtype=dtype, typ=typ, consolidate=copy) File c:\Users\matianht\.conda\envs\nomura\lib\site-packages\pandas\core\internals\construction.py:114, in arrays_to_mgr(arrays, columns, index, dtype, verify_integrity, typ, consolidate) 111 if verify_integrity: 112 # figure out the index, if necessary 113 if index is None: --> 114 index = _extract_index(arrays) 115 else: 116 index = ensure_index(index) File c:\Users\matianht\.conda\envs\nomura\lib\site-packages\pandas\core\internals\construction.py:667, in _extract_index(data) 664 raise ValueError("Per-column arrays must each be 1-dimensional") 666 if not indexes and not raw_lengths: --> 667 raise ValueError("If using all scalar values, you must pass an index") 669 if have_series: 670 index = union_indexes(indexes) ValueError: If using all scalar values, you must pass an index 这是什么意思
最新发布
07-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值