脚本的安全问题初探

  在linux常用的脚本很多,例如shell几乎是linux必备的脚本。脚本也是一种可执行文件,因此,它也面临这安全类的问题。脚本从它产生的情况来看,可以分为两类:

  一类是静态的,就是脚本是以前写好的,而且运行时只需要执行该脚本。以后不会去修改它。这是很常见的。很多linux服务器在部署的时候,这些脚本就会被部署。

  另一类是动态的,也就是脚本本省并不是部署好的,而是其他程序在运行时动态生成的,,然后保存成临时脚本的形式,生成程序或其他程序来执行它。这种技术也是普遍存在的,可能编程的人认为这种方式实现某些问题比较简单,从而采取了这种设计。事实上,这种脚本会带来严重的安全问题,而且难以解决。

  对于第一类脚本,解决方法比较多。例如加密,签名等方法都可以增加安全性。例如,使用非对称加密RSA私钥给静态的脚本进行签名,服务器上放置公钥,脚本在运行时,使用公钥解密签名,进行验证。这种方式是足够安全的,而且也不复杂。只需要修改一下脚本解释器,在执行脚本的时候添加验证逻辑就行。当然,这种情况只限于使用了静态脚本的服务器上。这种方式下,任何脚本都会被要求强制进行签名校验。(前提当然是脚本将解释器不会被替换,这也可以使用签名验证的方式在二进制可执行文件上,这里就不讨论这方面的问题,配合签名和lsm模块,可以实现二进制可执行文件的签名校验)。

  然而如果服务器上的程序使用了动态脚本技术,很显然程序生成的脚本没有被签名,从而执行也会失败。因此,动态脚本需要重新考虑。

  动态脚本本质上的问题就是动态生成脚本身份的确认问题,也就是如何确认这个脚本是由程序动态生成的。关于身份确认的问题,肯定离不开签名,第一个解决方案也是基于此的:如果程序生成动态脚本的时候,同时生成一个数字签名,这样子脚本解释器执行的时候验证数字签名就行了。为了防止入侵者自己写一个脚本,然后同样生成一个数字签名而来欺骗脚本解释器,因此这里的数字签名算法必须被保护,也就是入侵者不能使用该方法同样给其他的脚本进行签名。想到这里,大家肯定都想,我改写一下某个数字签名算法,比如sha1,然后将改写的部分进行保护。这样安全性就依赖改写的sha1算法被保护的程度了。考虑这样一个场景,某cgi程序a和bash使用了同样的改写的sha1算法计算数字签名,这样它们就可以配合工作。因此a和bash中都有着改写的sha1算法的实现。这问题就来了,逆向工程使得它们是极其的不安全。a或者bash被逆向后,就可以分析出改写的sha1算法的实现。因此,必须要增加逆向难度,例如使用模糊技术,加壳技术使得逆向过程难度增大。但并不意味着逆向不可能。或许你们公司使用先进的加壳技术以及对代码进行复杂的模糊处理使得逆向实际上变得不可能也是可以的。笔者为了测试一下模糊代码对反编译确实能造成多大的困难,便做了一个很简单的测试。也就是对sha1算法做了一个简单的处理,按照一个简单的规则改了下缓冲区中的数据,然后在计算sha1.主要是看代码反编译后的样子。程序源代码如下:

#include <openssl/sha.h>#include <assert.h>#include <stdio.h>#include <sys/stat.h>#include <stdlib.h>#include <string.h>#include <time.h>#include “variant_sha1.h”#define SHA_SWAP_BUFF_LEN 3//sha1计算过程中的临时变量的长度#define VARIANT_CONST 10//变异算法中使用的常量/*** 获取文件大小* @param filename 是输入文件* @return -1表示异常,>0表示文件大小*/static intget_file_size(const char* filename){struct stat buf;if (filename == NULL)return -1;if (stat(filename, &buf)<0)return -1;return buf.st_size;}/** 用于对data_buff数据进行变异处理,为了增加反编译难度,该函数内大量* 使用了goto来增加流程模糊的效果.该函数不符合checklist,目的是保护代码* @data_buff 缓冲区指针* @length 缓冲区长度 由调用这保证前置条件 data_buff != NULL 以及 length >=0*/void static variant_buff(char* data_buff, int length){assert(data_buff);assert(length >= 0);int gotoflag = length & (~length) + 1;int location = gotoflag – (gotoflag & (~gotoflag));int i = 0;int j = 0;int k =0;begin:if (length < 0){goto length_error;}else{srand((unsigned)time(NULL));gotoflag = rand() % (VARIANT_CONST * VARIANT_CONST + length % (rand() % VARIANT_CONST + 1) + 1);if (gotoflag > 0){gotoflag = 0;goto handle;}else{goto begin;}}handle:if ((length & 1) == 0){srand((unsigned)time(NULL));gotoflag = rand() % (VARIANT_CONST * VARIANT_CONST + length % (rand() % VARIANT_CONST + 1) + 1);if (gotoflag > 0){gotoflag = 0;goto even_handle;}else{goto begin;}}else{srand((unsigned)time(NULL));gotoflag = (rand() + SHA_SWAP_BUFF_LEN) % (VARIANT_CONST * VARIANT_CONST + length % (rand() % VARIANT_CONST + 1) + 1);if (gotoflag > 0){gotoflag = 0;goto odd_handle;}else{goto begin;}}//实际变异处理的代码even_handle:if (length == 0){goto length_error;}i = (length -1) >> 1;j = i / VARIANT_CONST;k = i – j * VARIANT_CONST;i = j;while (k > 0){*(data_buff + k) = (*(data_buff + k) + k);j = i / VARIANT_CONST;k = i – j * VARIANT_CONST;i = j;}if (k == 0){*(data_buff + k) = (*(data_buff + k) + VARIANT_CONST);}length = length >> 1;if ((length & 1) == 0){length += 1;}goto begin;odd_handle:i = (length >> 1) – 1;j = i / (VARIANT_CONST >> 1);k = i – j * (VARIANT_CONST >> 1);i = j;while (k > 0){//printf(“k=%d\n”,k);*(data_buff + k) = (*(data_buff + k) + k);j = i / (VARIANT_CONST >> 1);k = i – j * (VARIANT_CONST >> 1);i = j;}if (k == 0){*(data_buff + k) = (*(data_buff + k) + VARIANT_CONST);}length_error:if (gotoflag >0){goto begin;}else{return;}}接受失败等于回归真实的自我,接受失败等于打破完美的面具,

脚本的安全问题初探

相关文章:

你感兴趣的文章:

标签云: