WordPress的Country State City Dropdown CF 7插件是一款用于WordPress网站的插件,它可以与Contact Form 7(CF 7)表单插件配合使用,为用户提供了一个方便的方式来在表单中选择国家、州/省和城市。近期WordPress Country State City Dropdown CF7插件被爆出在版本2.7.2及之前的版本中存在SQL注入漏洞(CVE-2024-3495),本篇文章围绕该漏洞展开学习。
WordPress Country State City Dropdown CF7插件在版本2.7.2及之前的版本中存在SQL注入漏洞(CVE-2024-3495)未经身份验证的远程攻击者可利用此漏洞获取数据库敏感信息。该漏洞是因为用户提供的参数未经足够转义并且现有SQL查询未经充分准备。这使得未经身份验证的攻击者可以将额外的SQL查询附加到已存在的查询中,从而提取数据库中的敏感信息。
影响版本:Country State City Dropdown CF7<=2.7.2
Country State City Dropdown CF7插件下载地址
下载插件后直接上传至wordpress中即可。
一、nonce:
首先了解一下wordpress中nonce的作用是什么?nonce字面意思一般指的是“使用一次的数字”,而在wordpress中nonce主要用于通过将查询字符串嵌入到URL 和表单来工作,确保请求有效并且来自合法用户。细节请看此篇文章
在wordpress中nonce的生成:
通过调用wp_create_nonce函数生成nonce值,首先根据传入的$action判断action是否有效,下来根据当前用户登录状态返回uid,如果是登录状态则传回登录uid,如果没有登陆uid赋值为0通过'nonce_user_logged_out'
过滤器来修改uid,再使用wp_hash() 函数生成nonce值。nonce值与当前用户uid和session绑定。(在WordPress中切换当前用户的身份,可以根据用户ID或用户名来指定,意味着无论是否登录,都可以生成nonce)
nonce有效期默认为12h
二、未授权sql注入
根据公开poc定位到问题所在位置wp-content/plugins/country-state-city-auto-dropdown/includes/ajax-actions.php:
这里主函数定义了tc_csca_get_states、tc_csca_get_cities函数,任选一个tc_csca_get_cities函数进行分析。先是使用check_ajax_referer函数对传入的nonce与后端生成的nonce进行校验。下来直接就定义全局变量$wpdb,用于后续sql查询。然后通过一个if语句判断是否设置了名为 sid
的 POST 参数,存在将其值赋给变量 $sid
。其中sanitize_text_field()
函数用于清理和过滤输入。最后把经过处理的sid值代入sql查询语句。下面跟进看下这里的逻辑。
跟进check_ajax_referer函数,可以看到此函数用于检验AJAX 请求中的安全性,主要校验传参进来的nonce与后端针对action生成得nonce是否相对应。逻辑如下:首先检查是否传入有效的action,然后从传入的参数中提取nonce值,下来使用 wp_verify_nonce()
函数来验证提取到的 nonce 的有效性,将 nonce 与指定的 $action
进行比较,并返回验证结果。验证完成后,函数触发一个动作钩子 check_ajax_referer
,传递 $action
和验证结果 $result
,最后如果验证失败且 $stop
参数为 flase
函数终止并输出错误信息。
$wpdb->prepare()
方法用于准备 SQL 查询语句, %1s
是占位符,用于替换为传入的 $sid
参数的值,将整个查询语句准备好以供执行。查询语句构造是在wprdpress数据库city表中设置stste_id为传入的sid进行查询。
通过调用 $wpdb->get_results()
方法执行查询,并将结果存储在 $cities
变量中。对WordPress 数据库操作感兴趣的可以参考此篇手册,这里不做过多解释。
其中sanitize_text_field()
函数主要用于过滤输入的字符,保证输入的参数为字符串形式没有特殊符号也没有‘%’编码形式得字符。首先确认传入得$sid不是对象或者数组,如果是返回空,并把传入的字符强制转换为字符串形式。下来用内置filter过滤无效的 UTF-8 字符。后又过滤了'<'、换行符、制表符、回车符、连续空格、百分号编码字符,把这些特殊字符用单个空格替换。这里过滤不全,过滤的空格可以使用注释符替换,过滤百分号编码,那么我们可以尝试其他编码形式去绕过。
那么攻击者怎么获取到有效的nonce进行攻击呢?下来继续看源码。
已知nonce是通过wp_create_nonce生成得,那么全局搜索看看哪里调用该函数,通过搜索比对发现在country-state-city-auto-dropdown/trunk/includes/include-js-css.php中CF7插件把在后端生成的nonce通过wp_locallize函数callback到前端:
先通过 wp_enqueue_script()
函数在 WordPress 前端页面中添加脚本和本地化数据。接着使用 wp_localize_script()
函数为js添加 ajax_url
和 nonce
本地化数据并且分别指定了 WordPress 后台 AJAX 请求的 URL 和安全性验证。这些数据会传递到 JavaScript 文件中,以便在前端 JavaScript 中可以使用。最后,通过 add_action('wp_enqueue_scripts', 'tc_csca_embedCssJs')
将该函数挂载到 WordPress 的 'wp_enqueue_scripts'
动作上,以确保在前台页面加载时执行。
综上所述:
当前用户的nonce值无法被攻击者获取,但是生成的nonce会被传送到前端,所以可以在前端全局搜索获取nonce值进行利用。而在主函数中,仅检验nonce未检验当前用户是否有管理员权限,而对于传入的参数过滤不完全和可直接拼接造成了sql注入。所以导致未授权sql注入漏洞存在。
一、在首页源码中找到nonce
二、poc验证
POST /wordpress/wp-admin/admin-ajax.php HTTP/1.1
Host: xxxxxxx
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wordpress_test_cookie=WP%20Cookie%20check; Hm_lvt_8acef669ea66f479854ecd328d1f348f=1716965892; Hm_lpvt_8acef669ea66f479854ecd328d1f348f=1717138760
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 172
action=tc_csca_get_cities&nonce_ajax=edd043e9ec&sid=1+or+0+union+select+concat(0x64617461626173653a,database(),0x7c76657273696f6e3a,version(),0x7c757365723a,user()),2,3--+-
添加了管理员权限验证,使用if判断语句配合函数current_user_can()函数检查当前用户是否具有编辑、查看等管理员操作权限。有权限才可以继续执行下面的代码,没有权限则会返回 'message' => 'Not Allowed'
在查询语句的时候使用单引号把占位符括起来'%1s',防止 SQL 注入攻击。
更新之后nonce还是会传回前端
但是因为加了权限认证,直接调用tc_csca_get_ciies会返回not allowed。
79 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!