基于wireshark打造安全分析师工具--解析suricata中的分析结果

从本篇文章开始,我将通过若干篇文章陆续介绍在实际安全运营的过程中,基于wireshark打造安全分析师趁手的流量威胁分析工具,帮助安全分析人员在面对网络数据包取证和分析时候达到事半功倍的效果。本篇文件介绍使用在使用iwreshark分析数据包事后,在UI上显示suricata的告警数据。

wireshark是最为常见的数据包分析工具,wireshark 的优势在于提供了最为广泛的协议解析能力,如下图1是wireshark支持的协议种类:
在这里插入图片描述
图1

可以看到支持的协议种类高达2000+。wireshark能供精确的解析协议,但是并没有能力判定协议每一个字段是否存在问题,某个会话过程是否是正常的。对于安全分析师来说,通常关注对于如下信息的判断:

  • 目的IP和源IP是否正常 ,例如为攻击者资产
  • HTTP中的URL是否存在风险,例如是否存在命令执行,注入,目录穿越等风险
  • 访问域名是否正常,例如是否为新注册的域名
  • TLS证书是否正常,例如是否为恶意组织申请的数字证书
  • HTTP,FTP,SMB等协议传输的文件是否正常 ,例如是否在传输病毒文件
  • SMB协议中是否存在风险,例如存在psexec利用

也许有经验的分析师能够根据HTTP等协议中的文本特征判断出常见的WEB类型的攻击,比如各种注入,命令执行,文件上传等威胁。但是IP地址是否存在风险,域名归属,证书是否正常,传输中文件是否恶意,SMB协议是否存在永恒之蓝等漏洞,对于绝大多数的安全分析师来说只能依赖于安全设备的检测给出答案。但是每一家安全设备的能力参差不齐,同时还需要兼顾误报,能力不可能非常的激进。

因此对于安全分析师们来说,一方面针对安全设备的告警数据包,构建本地的安全分析工具进行二次验证,丰富对于告警数据包不同检测点的识别,增强对于威胁的确认。另一方面针对一些需要调查的数据包,使用本地安全工具进行分析,找出潜在的威胁是很有帮助的。

针对数据包的调查取证来说,首要的是要能够解码数据包,wireshark的丰富协议解析能力是首选。针对威胁检测方面,数据包领域的开源威胁检测引擎包括suricata,snort,zeek,针对这三种引擎的使用,见这里。针对安全分析师来说,要做的就是将wireshark的协议解析能力和suricata的威胁分析能力进行结合。整体的思路很简单,利用wireshark的插件能力,将数据包的suricata分析结果融合进来,如下图2:
在这里插入图片描述

图2

可以看到,suricata的告警结果可以作为整个数据包层次的最顶层,本质其是对于该数据包中存在的攻击手段,威胁类型,威胁等级进行说明,让分析师在分析改流的时候第一时间了解攻击上下文。相应的插件代码如下,或者到该链接进行下载:

local ok, json = pcall(require, "cjson")
if not ok then
	json = require("dkjson")
end

--注册需要在wireshark UI上显示suricata eve文件中的信息,包括alert告警和主要协议信息
if (gui_enabled()) then 
	local suri_proto = Proto("suricata", "Suricata Analysis")
	local suri_sid = ProtoField.string("suricata.alert.sid", "SID", FT_INTEGER)
	local suri_msg = ProtoField.string("suricata.alert.msg", "Message", FT_STRING)
	local suri_category = ProtoField.string("suricata.alert.category", "Category", FT_INTEGER)
	local suri_severity = ProtoField.string("suricata.alert.severity", "Severity", FT_INTEGER)

	local suri_tls_subject = ProtoField.string("suricata.tls.subject", "TLS subject", FT_STRING)
	local suri_tls_issuerdn = ProtoField.string("suricata.tls.issuerdn", "TLS issuer DN", FT_STRING)
	local suri_tls_fingerprint = ProtoField.string("suricata.tls.fingerprint", "TLS fingerprint", FT_STRING)
	local suri_tls_version = ProtoField.string("suricata.tls.version", "TLS version", FT_STRING)

	local suri_ssh_client_version = ProtoField.string("suricata.ssh.client.version", "SSH client version", FT_STRING)
	local suri_ssh_client_proto = ProtoField.string("suricata.ssh.client.proto", "SSH client protocol", FT_STRING)
	local suri_ssh_server_version = ProtoField.string("suricata.ssh.server.version", "SSH server version", FT_STRING)
	local suri_ssh_server_proto = ProtoField.string("suricata.ssh.server.proto", "SSH server protocol", FT_STRING)

	local suri_fileinfo_filename = ProtoField.string("suricata.fileinfo.filename", "Fileinfo filename", FT_STRING)
	local suri_fileinfo_magic = ProtoField.string("suricata.fileinfo.magic", "Fileinfo magic", FT_STRING)
	local suri_fileinfo_md5 = ProtoField.string("suricata.fileinfo.md5", "Fileinfo md5", FT_STRING)
	local suri_fileinfo_sha1 = ProtoField.string("suricata.fileinfo.sha1", "Fileinfo sha1", FT_STRING)
	local suri_fileinfo_sha256 = ProtoField.string("suricata.fileinfo.sha256", "Fileinfo sha256", FT_STRING)
	local suri_fileinfo_size = ProtoField.string("suricata.fileinfo.size", "Fileinfo size", FT_INTEGER)
	local suri_fileinfo_stored = ProtoField.string("suricata.fileinfo.stored", "Fileinfo stored", FT_STRING)

	local suri_http_url = ProtoField.string("suricata.http.url", "HTTP URL", FT_STRING)
	local suri_http_hostname = ProtoField.string("suricata.http.hostname", "HTTP hostname", FT_STRING)
	local suri_http_user_agent = ProtoField.string("suricata.http.user_agent", "HTTP user agent", FT_STRING)
	local suri_http_content_type = ProtoField.string("suricata.http.content_type", "HTTP Content Type", FT_STRING)
	local suri_http_method = ProtoField.string("suricata.http.method", "HTTP Method", FT_STRING)
	local suri_http_protocol = ProtoField.string("suricata.http.protocol", "HTTP Protocol", FT_STRING)
	local suri_http_status = ProtoField.string("suricata.http.status", "HTTP Status", FT_STRING)
	local suri_http_length = ProtoField.string("suricata.http.length", "HTTP Length", FT_STRING)
	local suri_http_referer = ProtoField.string("suricata.http.referer", "HTTP Referer", FT_STRING)

	local suri_smb_command = ProtoField.string("suricata.smb.command", "SMB Command", FT_STRING)
	local suri_smb_filename = ProtoField.string("suricata.smb.filename", "SMB Filename", FT_STRING)
	local suri_smb_share = ProtoField.string("suricata.smb.share", "SMB Share", FT_STRING)
	local suri_smb_status = ProtoField.string("suricata.smb.status", "SMB Status", FT_STRING)

	local suri_prefs = suri_proto.prefs
	local suri_running = false

	local suri_alerts = {
    
    }

	local suricata_alert_expert_info = ProtoExpert.new("suricata.alert.expert","Suricata Alerts",expert.group.MALFORMED,expert.severity.WARN)
	local suricata_proto_expert_info = ProtoExpert.new("suricata.proto.expert","Suricata Proto Info",expert.group.DEBUG,expert.severity.NOTE)
	--注册分析下的专家信息
	suri_proto.experts = {
    
    suricata_alert_expert_info,suricata_proto_expert_info}

	suri_prefs.alert_file = Pref.string("EVE file", "/var/log/suricata/eve.json",
					    "EVE file containing information about pcap")
	suri_proto.fields = {
    
    suri_sid, suri_msg, suri_category, suri_severity, suri_tls_subject, suri_tls_issuerdn, suri_tls_fingerprint, suri_tls_version,
				suri_ssh_client_version, suri_ssh_client_proto, suri_ssh_server_version, suri_ssh_server_proto,
				suri_fileinfo_filename, suri_fileinfo_magic, suri_fileinfo_md5, suri_fileinfo_sha1, suri_fileinfo_sha256,
				suri_fileinfo_size, suri_fileinfo_stored, 
				suri_http_url, suri_http_hostname, suri_http_user_agent,
				suri_http_content_type, suri_http_method, suri_http_protocol, suri_http_status, suri_http_length, suri_http_referer,
				suri_smb_command, suri_smb_filename, suri_smb_share, suri_smb_status
				}

	
	--在解析每个数据包的同时,将已经解析的eve log数据按照数据包的序号为索引填充到已经注册的字段中
	function suri_proto.dissector(buffer,pinfo,tree)
		if not(suri_alerts[pinfo.number] == nil) then
			for i, val in ipairs(suri_alerts[pinfo.number]) do
				if val['sid'] then
					--print(val['sid'])
					subtree = tree:add(suri_proto,
							"Suricata alert: "..val['sid'].." ("..val['msg']..")")
					
					subtree:add(suri_sid, val['sid'])
					subtree:add(suri_msg, val['msg'])
					subtree:add(suri_category, val['category'])
					subtree:add(suri_severity, val['severity'])
					subtree:add_proto_expert_info(suricata_alert_expert_info, val['msg'])
				end
				if val['tls_subject'] then
					subtree = tree:add(suri_proto, "Suricata TLS Info")

					subtree:add(suri_tls_subject, val['tls_subject'])
					subtree:add(suri_tls_issuerdn, val['tls_issuerdn'])
					subtree:add(suri_tls_fingerprint, val['tls_fingerprint'])
					subtree:add(suri_tls_version, val['tls_version'])
					subtree:add_proto_expert_info(suricata_proto_expert_info, "TLS info")
				end
				if val['ssh_client_version'] then
					subtree = tree:add(suri_proto, "Suricata SSH Info")

					subtree:add(suri_ssh_client_version, val['ssh_client_version'])
					subtree:add(suri_ssh_client_proto, val['ssh_client_proto'])
					subtree:add(suri_ssh_server_version, val['ssh_server_version'])
					subtree:add(suri_ssh_server_proto, val['ssh_server_proto'])
					subtree:add_proto_expert_info(suricata_proto_expert_info, "SSH info")
				end
				if val['fileinfo_filename'] then
					subtree = tree:add(suri_proto, "Suricata File Info")

					subtree:add(suri_fileinfo_filename, val['fileinfo_filename'])
					if val['fileinfo_magic'] then
						subtree:add(suri_fileinfo_magic, val['fileinfo_magic'])
					end
					if val['fileinfo_md5'] then
						subtree:add(suri_fileinfo_md5, val['fileinfo_md5'])
					end
					if val['fileinfo_sha1'] then
						subtree:add(suri_fileinfo_sha1, val['fileinfo_sha1'])
					end
					if val['fileinfo_sha256'] then
						subtree:add(suri_fileinfo_sha256, val['fileinfo_sha256'])
					end
					subtree:add(suri_fileinfo_size, val['fileinfo_size'])
					if val['fileinfo_stored'] then
						subtree:add(suri_fileinfo_stored, val['fileinfo_stored'])
					end
					subtree:add_proto_expert_info(suricata_proto_expert_info, "File info")
				end
				if val['http_url'] then
					--print(val['http_url'])
					subtree = tree:add(suri_proto, "Suricata HTTP Info")
					-- add protocol fields to subtree
					subtree:add(suri_http_url, val['http_url'])
					if val['http_hostname'] then
						subtree:add(suri_http_hostname, val['http_hostname'])
					end
					if val['http_user_agent'] then
						subtree:add(suri_http_user_agent, val['http_user_agent'])
					end
					if val['http_content_type'] then
						subtree:add(suri_http_content_type, val['http_content_type'])
					end
					if val['http_method'] then
						subtree:add(suri_http_method, val['http_method'])
					end
					if val['http_protocol'] then
						subtree:add(suri_http_protocol, val['http_protocol'])
					end
					if val['http_status'] then
						subtree:add(suri_http_status, val['http_status'])
					end
					if val['http_length'] then
						subtree:add(suri_http_length, val['http_length'])
					end
					if val['http_referer'] then
						subtree:add(suri_http_referer, val['http_referer'])
					end

					subtree:add_proto_expert_info(suricata_proto_expert_info, "HTTP info")
				end
				if val['smb_command'] then
					subtree = tree:add(suri_proto, "Suricata SMB Info")
					subtree:add(suri_smb_command, val['smb_command'])
					if val['smb_filename'] then
						subtree:add(suri_smb_filename, val['smb_filename'])
					end
					if val['smb_share'] then
						subtree:add(suri_smb_share, val['smb_share'])
					end
					if val['smb_status'] then
						subtree:add(suri_smb_status, val['smb_status'])
					end
					subtree:add_proto_expert_info(suricata_proto_expert_info, "SMB info")
				end
		     end
	     end
	end

	function suri_proto.init()
	end

	-- 解析suricata eve log日志
	function ids_load_log(eve_file)
		function suricata_eve_log_parser(file)
			local event
			local id = 0
			local s_text = ""
			suri_alerts = {
    
    }
			for s_text in io.lines(file) do
				event = json.decode(s_text)
				id = event["pcap_cnt"]
				if not (id == nil) then
					if event["event_type"] == "alert" then
						if suri_alerts[id] == nil then
							suri_alerts[id] = {
    
    }
						end
						table.insert(suri_alerts[id],
							{
    
    category = event["alert"]["category"], sid = tonumber(event["alert"]["signature_id"]),
							severity = tonumber(event["alert"]["severity"]), msg = event["alert"]["signature"]})
					elseif event["event_type"] == "tls" then
						if suri_alerts[id] == nil then
							suri_alerts[id] = {
    
    }
						end
						table.insert(suri_alerts[id],
							{
    
     tls_subject = event["tls"]["subject"], tls_issuerdn = event["tls"]["issuerdn"],
							tls_fingerprint = event["tls"]["fingerprint"], tls_version = event["tls"]["version"]})
					elseif event["event_type"] == "ssh" then
						if suri_alerts[id] == nil then
							suri_alerts[id] = {
    
    }
						end
						table.insert(suri_alerts[id],
							{
    
     ssh_client_version = event["ssh"]["client"]["software_version"],
							ssh_client_proto = event["ssh"]["client"]["proto_version"],
							ssh_server_version = event["ssh"]["server"]["software_version"],
							ssh_server_proto = event["ssh"]["server"]["proto_version"],
							})
					elseif event["event_type"] == "fileinfo" then
						if suri_alerts[id] == nil then
							suri_alerts[id] = {
    
    }
						end
						table.insert(suri_alerts[id],
							{
    
     fileinfo_filename = event["fileinfo"]["filename"],
							  fileinfo_magic = event["fileinfo"]["magic"],
							  fileinfo_md5 = event["fileinfo"]["md5"],
							  fileinfo_sha1 = event["fileinfo"]["sha1"],
							  fileinfo_sha256 = event["fileinfo"]["sha256"],
							  fileinfo_size = tonumber(event["fileinfo"]["size"]),
							  fileinfo_stored = tostring(event["fileinfo"]["stored"]),
							})
					elseif event["event_type"] == "http" then
						if suri_alerts[id] == nil then
							suri_alerts[id] = {
    
    }
						end
						table.insert(suri_alerts[id],
							{
    
    
								http_url = event["http"]["url"],
								http_hostname = event["http"]["hostname"],
								http_user_agent = event["http"]["http_user_agent"],
								http_content_type = event["http"]["http_content_type"],
								http_method = event["http"]["http_method"],
								http_protocol = event["http"]["protocol"],
								http_status = event["http"]["status"],
								http_length = event["http"]["length"],
								http_referer = event["http"]["http_refer"]
							})
					elseif event["event_type"] == "smb" then
						if suri_alerts[id] == nil then
							suri_alerts[id] = {
    
    }
						end
						table.insert(suri_alerts[id],
							{
    
    
								smb_command = event["smb"]["command"],
								smb_filename = event["smb"]["filename"],
								smb_share = event["smb"]["share"],
								smb_status = event["smb"]["status"],
							})
					end
				end
			end
		end


		function suriwire_register(file)
			if file == "" then
				file = suri_prefs.alert_file
			end
			local filehandle = io.open(file, "r")

			if not (filehandle == nil) then
				filehandle:close()
				-- parse suricata log file
				suricata_eve_log_parser(file)
				-- register protocol dissector
				if suri_running == false then
					register_postdissector(suri_proto)
					suri_running = true
				end
				reload()
			else
				new_dialog("Unable to open '" .. file
					   .. "'. Choose another alert file",
					   suriwire_register,
					   "Choose file (Linux default:" .. suri_prefs.alert_file..")")
			end
		end
		-- run suricata
		-- set input file
		if eve_file then
			suriwire_register(eve_file)
		else
			new_dialog("Choose alert file",
			           suriwire_register,
			           "Choose file (Linux default:" .. suri_prefs.alert_file..")")
		end
	end

	function wireshark_page()
		browser_open_url("https://blog.csdn.net/javajiawei/category_9583097.html")
	end

	register_menu("导入入侵检测日志/导入Suricata日志", ids_load_log, MENU_TOOLS_UNSORTED)
	register_menu("Wireshark使用技巧", wireshark_page, MENU_TOOLS_UNSORTED)
	local eve_file = os.getenv("SURIWIRE_EVE_FILE")
	if eve_file then
		ids_load_log(eve_file)
	end
end

需要注意的是由于该脚本中引用了dkjson的lua脚本,因此需要先将该Lua脚本下载到本地wireshark目录,下载地址,这里 。或者到对应的github下载,这里

Lua插件的加载方式为使用命令行打开wireshark,如下:

wireshark -r test.pcap -X lua_script:suricata.lua

或者将改脚本配置到init.lua中,进行自启动,init.lua中添加如下配置:

扫描二维码关注公众号,回复: 15406818 查看本文章
disable_lua = false--开启Lua解析
dofile(DATA_DIR.."suricata.lua")

启动wireshark之后,需要加载suricata的eve log文件,如下图3导入suricata的日志:
在这里插入图片描述

图3
加载对应的eve log之后,在专家信息中会生成对应的告警信息的解析,如下图4:
在这里插入图片描述

图4
图4中显示了所有出发告警的数据包信息,选择对应的告警就可以跳转到对应的数据包。图3中可以看到需要输入的文件为suricata eve格式的文件,示例如下:

{"timestamp":"2019-10-15T06:54:14.857081+0000","flow_id":1511806696075679,"pcap_cnt":351,"event_type":"alert","src_ip":"1.1.141.169","src_port":17725,"dest_ip":"1.2.61.25","dest_port":53,"proto":"TCP","alert":{"action":"allowed","gid":1,"signature_id":2030524,"rev":1,"signature":"ET INFO Possible NOP Sled Observed in Large DNS over TCP Packet M1","category":"Attempted Administrator Privilege Gain","severity":1,"metadata":{"affected_product":["Windows_DNS_server"],"created_at":["2020_07_15"],"former_category":["INFO"],"performance_impact":["Significant"],"signature_severity":["Informational"],"updated_at":["2020_07_15"]}},"flow":{"pkts_toserver":3,"pkts_toclient":1,"bytes_toserver":1638,"bytes_toclient":62,"start":"2019-10-15T06:54:14.700831+0000"}}
{"timestamp":"2019-10-15T06:54:14.872549+0000","flow_id":535741018300164,"pcap_cnt":372,"event_type":"dcerpc","src_ip":"1.1.171.176","src_port":7018,"dest_ip":"1.2.34.235","dest_port":6502,"proto":"TCP","dcerpc":{"request":"BIND","interfaces":[{"uuid":"62b93df0-8b02-11ce-876c-00805f842837","version":"1.0","ack_result":0}],"response":"BINDACK","call_id":0,"rpc_version":"5.0"}}
{"timestamp":"2019-10-15T06:54:14.902033+0000","flow_id":1632151679679309,"pcap_cnt":386,"event_type":"anomaly","src_ip":"1.2.33.242","src_port":80,"dest_ip":"1.1.30.100","dest_port":46189,"proto":"TCP","tx_id":0,"anomaly":{"app_proto":"http","type":"applayer","event":"INVALID_REQUEST_CHUNK_LEN","layer":"proto_parser"}}
{"timestamp":"2019-10-15T06:54:14.902033+0000","flow_id":1632151679679309,"pcap_cnt":386,"event_type":"http","src_ip":"1.1.30.100","src_port":46189,"dest_ip":"1.2.33.242","dest_port":80,"proto":"TCP","tx_id":0,"http":{"hostname":"pdfyoocaugsebmnhamlz","url":"/","http_method":"GET","protocol":"HTTP/1.1","length":0}}
{"timestamp":"2019-10-15T06:54:14.914528+0000","flow_id":535741018300164,"pcap_cnt":390,"event_type":"dcerpc","src_ip":"1.1.171.176","src_port":7018,"dest_ip":"1.2.34.235","dest_port":6502,"proto":"TCP","dcerpc":{"request":"REQUEST","req":{"opnum":43,"frag_cnt":1,"stub_data_size":20},"response":"RESPONSE","res":{"frag_cnt":1,"stub_data_size":8},"call_id":0,"rpc_version":"5.0"}}

可以看到上述脚本针对eve格式中eventype为alert,http,dns,tls,smb等类型进行了解析。alert类型告警的多少依赖于规则库的丰富程度,这块安全分析师需要个人进行积累,必要的时候需要自己进行定义。同时脚本针对HTTP,DNS,TLS,SMB等协议记录的关键字段也进行了解析,如上图2。至于如何判断这些字段是否存在问题,以及根据这些字段能够得出哪些更为丰富的信息,后续的文章会进行介绍。

关于如何获取eve log文件,可以参考文章,这里。需要注意的事,在dalton的默认容器中有很多的信息是不默认记录的,如果想要记录更多的信息需要修改suricata.yaml中的默认配置,如下图6是记录文件md5值的配置信息,见这里
在这里插入图片描述
图6
可以根据实际的需要修改yaml文件字段,生成eve log之后,对应修改lua,解析对应的字段即可。

需要注意的是入侵检测的规则主要针对的威胁形式为具备某种检测点的攻击技术点,例如SQL注入,命令执行,反序列化等。针对IOC,恶意文件传输,恶意证书等指标类的威胁特征,虽然也可以编写IDS规则,但是一般采用威胁情报碰撞的形式进行检测更为的方便,主要原因在于IOC的特征数量大,变换较为迅速,将会在下一篇文章中进行介绍。

本文为CSDN村中少年原创文章,未经允许不得转载,博主链接这里

猜你喜欢

转载自blog.csdn.net/javajiawei/article/details/130176195