博客地址:
https://www.freebuf.com/articles/web/213327.html
項目地址:
https://github.com/Lonely-night/fastjson_gadgets_scanner
修改裏面的反編譯之後的存放java源代碼的路徑;以及fernflower的路徑,
先反編譯,
python3.6 decomplie_jar.py
然後使用掃描器進行掃描:
python3.6 scanner.py
/home/77/repos/tmp/source/HikariCP-2.7.9/com/zaxxer/hikari/HikariConfig.java getObjectOrPerformJndiLookup
/home/77/repos/tmp/source/commons-configuration-1.10/org/apache/commons/configuration/JNDIConfiguration.java containsKey
/home/77/repos/tmp/source/commons-configuration-1.10/org/apache/commons/configuration/JNDIConfiguration.java getProperty
43641
總共304個jar包,43641個Java源文件,工找到三個方法。其實可以繼續更細緻。
現在對別人寫的這個使用AST掃描目標目錄下jar包裏的gadget的方法。
抽象語法樹分析尋找FastJSON的Gadgets代碼分析
首先認爲fastjson/jackson中的gadget的sink點爲:
Object object = context.lookup(name);
其中context對象爲javax.naming.Context
或其子類的實例,而name爲一個jnid的url。
先是把~/.m2/respository目錄下的jar包拿到,然後用fernflower反編譯,得到壓縮的java源碼包,然後解壓,
首先從這個scanner函數開始:
from javalang.parse import parse
from javalang.tree import *
# 傳入的是一個目錄下的java源代碼文件
def scanner(filename):
file_stream = open(filename, 'r')
_contents = file_stream.read()
file_stream.close()
# 字符串判斷快速過濾
#(如果文件里根本沒有InitialContext(相關的內容,則直接返回False,不用浪費時間了)
if "InitialContext(" not in _contents:
return False
try:
# 使用javalang庫解析源代碼,得到抽象語法樹AST
root_tree = parse(_contents)
except:
return False
# 拿到滿足那三個條件的類聲明,可能不止一個,是一個list
class_declaration_list = get_class_declaration(root_tree)
# 遍歷類聲明
for class_declaration in class_declaration_list:
# 遍歷方法聲明
for method_declare in class_declaration.methods:
if ack(method_declare) is True:
string = "{file} {method}".format(file=filename, method=method_declare.name)
print string
write_file("./result.txt", string)
由於FastJSON的checkAutoType
方法對反序列化的類有三點限制:
- 1、不能繼承 Classloader;
- 2、不能實現 DataSource 和 RowSet 接口(在黑名單中);
- 3、必須有一個無參的構造函數。
於是這裏使用get_class_declaration()
用於找出符合這種特徵的類,看一下這個函數內容:
def get_class_declaration(root):
class_list = []
black_interface = ("DataSource", "RowSet")
for node in root.types:
# 非類聲明都不分析(類聲明被映射爲ClassDeclaration 對象)
if isinstance(node, ClassDeclaration) is False:
continue
# 判斷是否繼承自classloader
if node.extends is not None and node.extends.name == "ClassLoader":
continue
# 判斷是否實現被封禁的接口
interface_flag = False
if node.implements is None:
node.implements = []
for implement in node.implements:
if implement.name in black_interface:
interface_flag = True
break
if interface_flag is True:
continue
# 判斷是否存在無參的構造函數
constructor_flag = False
for constructor_declaration in node.constructors:
if len(constructor_declaration.parameters) == 0:
constructor_flag = True
break
if constructor_flag is False:
continue
class_list.append(node)
return class_list
拿到類聲明列表之後,遍歷得到類聲明(ClassDeclaration
),然後再對這個類聲明遍歷方法聲明(class_declaration.methods
)。
對於每個方法聲明,再使用ack
方法最後確認,
def ack(method_node):
"""
1、是否調用的lookup 方法,
2、lookup中參數必須是變量
3、lookup中的參數必須來自函數入參,或者類屬性
:param method_node:
:return:
"""
target_variables = []
for path, node in method_node:
# 是否調用lookup 方法
# (node爲方法調用,則方法名爲lookup)
if isinstance(node, MethodInvocation) and node.member == "lookup":
# 只能有一個參數。
# (判斷方法調用的參數個數)
if len(node.arguments) != 1:
continue #不是一個參數的,結束這次循環,下一個
# 參數類型必須是變量,且必須可控
arg = node.arguments[0]
if isinstance(arg, Cast): # 變量 類型強轉
target_variables.append(arg.expression.member)
if isinstance(arg, MemberReference): # 變量引用
target_variables.append(arg.member)
if isinstance(arg, This): # this.name, 類的屬性也是可控的
return True
if len(target_variables) == 0:
return False
# 判斷lookup的參數,是否來自於方法的入參,只有來自入參才認爲可控
for parameter in method_node.parameters:
parameter_name = parameter.name
if parameter_name in target_variables:
return True
return False
第一個for循環,拿到lookup方法調用的參數,判斷是否可控;(TODO:判斷這個lookup方法是否是javax.naming.Context類及其子類的實例調用的)
第二個for循環,判斷這個方法本身的參數與這個方法裏調用lookup的參數是一樣的。
學習javalang的API的筆記
node類
node.extends.name:類繼承的類的名字
node.implements:類實現的接口(list)
node.implements.name: 類實現的接口的名字
node.constructors:類的構造器(list)
for constructor in constructors: 類的構造器的參數列表(list)
constructor.parameters
node方法
isinstance(node, MethodInvocation):判斷某method節點是否是MethodInvocation類型
node.member:方法名
node.arguments:方法的參數列表(list)
isinstance(arg, Cast):arg爲變量 類型強轉
isinstance(arg, MemberReference):arg爲變量引用
isinstance(arg, This):arg爲this的屬性
檢測算法
for path, node in method_node:
# 是否調用lookup 方法
if isinstance(node, MethodInvocation) and node.member == "lookup":
# 只能有一個參數。
if len(node.arguments) != 1:
continue
# 參數類型必須是變量,且必須可控
arg = node.arguments[0]
if isinstance(arg, Cast): # 變量 類型強轉
target_variables.append(arg.expression.member)
if isinstance(arg, MemberReference): # 變量引用
target_variables.append(arg.member)
if isinstance(arg, This): # this.name, 類的屬性也是可控的
return True
if len(target_variables) == 0: #如果都不是,則認爲不可控
return False
# 判斷lookup的參數,是否來自於方法的入參,只有來自入參才認爲可控
for parameter in method_node.parameters:
if parameter.name in target_variables:
return True
return False