当前位置: 首页 > 科技观察

从nginx日志原始二进制数据还原文件

时间:2023-03-17 00:01:56 科技观察

nginx的accesslog自定义格式记录post请求数据,由于某些原因,需要将原始数据还原为jpg格式图片。首先对日志进行处理,过滤掉包含图像数据的日志条目,取出其中一条进行分析。一般格式如下。为了方便查看,做一个换行符:-|09/Dec/2017:08:00:19+0000|POST/some/apiHTTP/1.1|200|461|--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition:form-data;name=\x22name\x22\x0D\x0AContent-Type:text/plain;charset=UTF-8\x0D\x0A\x0D\x0value\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition:form-data;name=\x22file\x22;filename=\abc.jpg\x22\x0D\x0AContent-Type:application/octet-stream\x0D\x0AContent-Transfer-Encoding:binary\x0D\x0A\x0D\x0A\xFF\xD8\xFF\xE0\x00\x10JFIF\x00...\xD2_\xA0\x1A\x7F\xFF\xD9\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\x0D\x0A|42097|-|-|-|1.1.1.1|d1fkkbcd02eb|127.0。0.1:8888|0.034|0.123其中--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9为表格的分隔符域,\x0D\x0A为回车换行的转义字符,相当于\r而\n,\xFF\xD8\xFF\xE0\x00\x10JFIF\x00...\xD2_\xA0\x1A\x7F\xFF\xD9是原始图像数据。使用上记单行日志文件tmp.log进行调试:>>>f=open('tmp.log','rb')>>>data=f.read()>>>data"-|09/Dec/2017:08:00:19+0000|POST/some/apiHTTP/1.1|200|461|--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition:form-data;name=\x22name\x22\x0D\x0AContent-Type:text/plain;charset=UTF-8\x0D\x0A\x0D\x0value\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition:form-data;名称=\x22file\x22;文件名=\x221512806410245.jpg\x22\x0D\x0AContent-Type:application/octet-stream\x0D\x0AContent-Transfer-Encoding:binary\x0D\x0A\x0D\x0A\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00...\xBC'\xF1\x8C\xCC\x83,\xFAo\xD2_\xA0\x1A\x7F\xFF\xD9\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\x0D\x0A|42097|-|-|-|1.1.1.1|d1fkkbcd02eb|127.0.0.1:8888|0.034|0.123">>>打印数据-|09/Dec/2017:08:00:19+0000|POST/some/apiHTTP/1.1|200|461|--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition:form-data;名称=\x22name\x22\x0D\x0AContent-Type:text/plain;charset=UTF-8\x0D\x0A\x0D\x0value\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition:form-data;名称=\x22file\x22;文件名=\x221512806410245.jpg\x22\x0D\x0AContent-Type:application/octet-stream\x0D\x0AContent-Transfer-Encoding:binary\x0D\x0A\x0D\x0A\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00...\xBC'\xF1\x8C\xCC\x83,\xFAo\xD2_\xA0\x1A\x7F\xFF\xD9\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\x0D\x0A|42097|-|-|-|1.1.1.1|d1fkkbcd02eb|127.0.0.1:8888|0.034|0.123>>>f.close()即可查看出来,打印到屏幕上的转义符,比如“\x0D\x0A”,文件中原来的字符串其实是“\\x0D\\x0A”,因为“”是用来转义的,所以要显示的反斜杠本身必须转义。也就是说打印的时候,“\”被转义为“”,所以我们看到的是“\x0D”,实际文件中存储的是“\\0D”,而我们真正需要写入文件的,是转义字符“\x0D”,而不是字符串“\x0D”。由于文件是以二进制方式打开的,读取的是原始流,所以匹配时需要用“\\\\”来表示“\\”。使用re库处理日志:原始数据前面是Content-Transfer-Encoding:binary加两个\r\n,后面是一个\r\n跟上表格splitstring--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9-->>>importre>>>pf=re.compile('^.*Content-Transfer-Encoding:binary\\\\x0D\\\\x0A\\\\x0D\\\\x0A')>>>pb=re.compile('\\\\x0D\\\\x0A--.*$')>>>data=re.sub(pf,'',data)>>>data"\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00...\\xBC'\\xF1\\x8C\\xCC\\x83,\\xFAo\\xD2_\\xA0\\x1A\\x7F\\xFF\\xD9\\x0D\\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\\x0D\\x0A|42097|-|-|-|1.1.1.1|d1fkkbcd02eb|127.0.0.1:8888|0.034|0.123">>>data=re.sub(pb,'',data)>>>data"\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00...\\xBC'\\xF1\\x8C\\xCC\\x83,\\xFAo\\xD2_\\xA0\\x1A\\x7F\\xFF\\xD9"的匹配过程不严谨,假设原始数据中已经包含\\x0D\\x0A--,原来的数据会丢失,但目前在本例中并没有生效。比较麻烦的做法是先匹配表格分割字符串,然后根据这个分割数据,然后把两边多余的字符和回车换行删掉。成功提取原始数据后,数据被解码并写入.jpg文件。如果没有解码步骤,所有数据将作为字符串写入文件,不会被视为转义字符。简单做个测试:将“\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01”保存到文件test.log。>>>f=open('test.log','rb')>>>data=f.read()>>>data'\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\\x01\n'>>>printdata\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01>>>importcodecsasc>>>c.decode(data,'string_escape')'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\n'>>>printc.decode(data,'string_escape')......JFIF>>>读取的原始数据是两个反斜杠,print变成字符串是因为反斜杠被转义为一个字符。解码后的数据本身只有一个斜杠,打印的时候打印出来的是转义符本身,也就是乱码。然后您可以拆分文件并恢复图片。#!/usr/bin/envpythonimportcodecs,reifile='access.log'suffix='jpg'pf=re.compile('^.*Content-Transfer-Encoding:binary\\\\x0D\\\\x0A\\\\x0D\\\\x0A')pb=re.compile('\\\\x0D\\\\x0A--.*$')try:withopen(ifile,'rb')asf:number=1#***图片序号为1whileTrue:l=f.readline().strip()#读一行,去掉末尾的换行符\nifnotl:#读完文件退出循环,返回''breakl=re.sub(pf,'',l)#将数据前的字符替换为空l=re.sub(pb,'',l)#将数据后的字符替换为空img_file='.'.join([str(number),suffix])#图片文件名printimg_file#打印名称看进度withopen(img_file,'wb')asi:#解码写入文件i.write(codecs.decode(l,'string_escape'))number+=1#下一张图片的序号加1exceptIOError:passaccess.log包含多条日志,每条日志包含图片数据。日志很大,所以没有使用readlines()。本来使用list也是比较占内存的,这样处理比较慢。您必须等待程序读取整个文件。逐行读取日志后,提取图像数据,解码并写入文件。