IoT安全入门05

第五章 物联网之嵌入式Web

0x01 http协议讲解

1. Web通信原理介绍

客户端和服务端通信

image-20220825231628792

2. HTTP协议简介

HTTP(HyperText Transfer Protocol)超文本传输协议

HTTP工作流程

image-20220825231748757

HTTP请求报文格式

image-20220825231824748

HTTP相应报文格式

image-20220825231909571

HTTP常见请求方法

  • GET:请求指定页面信息,并返回实体主体;
  • POST:向指定资源提交数据并进行处理请求,数据被包含在请求体中,POST请求可能会导致新的资源的建立或已有资源的修改;
  • HEAD:类似GET请求,只不过返回的响应中没有具体内容,用于获取报头;
  • PUT:从客服端向服务器传送的数据取代指定的文档内容
  • DELETE:请求服务器删除指定的内容;
  • CONNECT:HTTP1.1协议中预留给能够将连接改为管道方式的代理服务器;
  • TRANCE:回显服务器收到的请求,主要用于测试或诊断;

HTTP常见Header

  • Host: www.test.com/ //请求的目标域名和端口号
  • Origin: http://localhost:8081/ //请求的来源域名和端口号 (跨域请求时,浏览器会自动带上这个头信息)
  • Referer: https:/localhost:8081/link?query=xxxxx //请求资源的完整URI
  • User-Agent //浏览器信息
  • Cookie: //当前域名下的Cookie
  • Accept: text/html,image/apng //代表客户端希望接受的数据类型是html或者是png图片类型
  • Accept-Encoding: gzip, deflate //代表客户端能支持gzip和deflate格式的压缩
  • Accept-Language: zh-CN,zh;q=0.9 //代表客户端可以支持语言zh-CN或者zh
  • Connection: keep-alive //告诉服务器,客户端需要的tcp连接是一个长连接
  • If-None-Match //如果内容未改变返回304代码,对应Etag
  • If-Modified-Since //对应last-midified,未被修改则返回304代码
  • Date: //服务端发送资源时的服务器时间
  • Expires: //缓存过期时间
  • Cache-Control: no-cache // 缓存方式
  • Etag // 文件内容hash
  • Last-Modified //最近一次文件修改时间
  • Content-Type: text/html; charset=utf-8 //编码格式
  • Content-Encoding: gzip //采用gzip对资源进行解码
  • Connection: keep-alive //tcp是长连接
  • Set-Cookie //设置Http Cookie

0x02 Tinyhttpd源码分析

Tinyhttpd是J. David Blackstone在1999年写的一个不到500行的超轻量型Http服务端程序,可以帮助我们真正理解服务器程序的本质。

Github仓库地址:https://github.com/nengm/Tinyhttpd

Tinyhttpd源码

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
/* J. David's webserver */
/* This is a simple webserver.
* Created November 1999 by J. David Blackstone.
* CSE 4344 (Network concepts), Prof. Zeigler
* University of Texas at Arlington
*/
/* This program compiles for Sparc Solaris 2.6.
* To compile for Linux:
* 1) Comment out the #include <pthread.h> line.
* 2) Comment out the line that defines the variable newthread.
* 3) Comment out the two lines that run pthread_create().
* 4) Uncomment the line that runs accept_request().
* 5) Remove -lsocket from the Makefile.
*/
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>

#define ISspace(x) isspace((int)(x))
//函数说明:检查参数c是否为空格字符,
//也就是判断是否为空格(' ')、定位字符(' t ')、CR(' r ')、换行(' n ')、垂直定位字符(' v ')或翻页(' f ')的情况。
//返回值:若参数c 为空白字符,则返回非 0,否则返回 0。


#define SERVER_STRING "Server: jdbhttpd/0.1.0rn"//定义server名称

void accept_request(int);//接收请求

void bad_request(int);//无效请求
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);

/**********************************************************************/
/* A request has caused a call to accept() on the server port to
* return. Process the request appropriately.
* Parameters: the socket connected to the client */
/**********************************************************************/
//接收客户端的连接,并读取请求数据
void accept_request(int client)
{
char buf[1024];
int numchars;
char method[255];
char url[255];
char path[512];
size_t i, j;
struct stat st;
int cgi = 0; /* becomes true if server decides this is a CGI
* program */
char *query_string = NULL;
//获取一行HTTP报文数据
numchars = get_line(client, buf, sizeof(buf));
//
i = 0; j = 0;
//对于HTTP报文来说,第一行的内容即为报文的起始行,格式为<method> <request-URL> <version>,
//每个字段用空白字符相连
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
//提取其中的请求方式是GET还是POST
method[i] = buf[j];
i++; j++;
}
method[i] = '';
//函数说明:strcasecmp()用来比较参数s1 和s2 字符串,比较时会自动忽略大小写的差异。
//返回值:若参数s1 和s2 字符串相同则返回0。s1 长度大于s2 长度则返回大于0 的值,s1 长度若小于s2 长度则返回小于0 的值。
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
//tinyhttp仅仅实现了GET和POST
unimplemented(client);
return;
}
//cgi为标志位,置1说明开启cgi解析
if (strcasecmp(method, "POST") == 0)
//如果请求方法为POST,需要cgi解析
cgi = 1;

i = 0;
//将method后面的后边的空白字符略过
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
//继续读取request-URL
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '';
//如果是GET请求,url可能会带有?,有查询参数
if (strcasecmp(method, "GET") == 0)
{
query_string = url;
while ((*query_string != '?') && (*query_string != ''))
query_string++;
if (*query_string == '?')
{
//如果带有查询参数,需要执行cgi,解析参数,设置标志位为1
cgi = 1;
//将解析参数截取下来
*query_string = '';
query_string++;
}
}
//以上已经将起始行解析完毕
//url中的路径格式化到path
sprintf(path, "htdocs%s", url);
//学习到这里明天继续TODO
//如果path只是一个目录,默认设置为首页index.html
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");

//函数定义: int stat(const char *file_name, struct stat *buf);
//函数说明: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
//返回值: 执行成功则返回0,失败返回-1,错误代码存于errno(需要include <errno.h>)
if (stat(path, &st) == -1) {
//假如访问的网页不存在,则不断的读取剩下的请求头信息,并丢弃即可
while ((numchars > 0) && strcmp("n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
//最后声明网页不存在
not_found(client);
}
else
{
//如果访问的网页存在则进行处理
if ((st.st_mode & S_IFMT) == S_IFDIR)//S_IFDIR代表目录
//如果路径是个目录,那就将主页进行显示
strcat(path, "/index.html");
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH) )
//S_IXUSR:文件所有者具可执行权限
//S_IXGRP:用户组具可执行权限
//S_IXOTH:其他用户具可读取权限
cgi = 1;
if (!cgi)
//将静态文件返回
serve_file(client, path);
else
//执行cgi动态解析
execute_cgi(client, path, method, query_string);

}

close(client);//因为http是面向无连接的,所以要关闭
}

/**********************************************************************/
/* Inform the client that a request it has made has a problem.
* Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{
char buf[1024];
//发送400
sprintf(buf, "HTTP/1.0 400 BAD REQUESTrn");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "Content-type: text/htmlrn");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "rn");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "<P>Your browser sent a bad request, ");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "such as a POST without a Content-Length.rn");
send(client, buf, sizeof(buf), 0);
}

/**********************************************************************/
/* Put the entire contents of a file out on a socket. This function
* is named after the UNIX "cat" command, because it might have been
* easier just to do something like pipe, fork, and exec("cat").
* Parameters: the client socket descriptor
* FILE pointer for the file to cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
//发送文件的内容
char buf[1024];
//读取文件到buf中
fgets(buf, sizeof(buf), resource);
while (!feof(resource))//判断文件是否读取到末尾
{
//读取并发送文件内容
send(client, buf, strlen(buf), 0);
fgets(buf, sizeof(buf), resource);
}
}

/**********************************************************************/
/* Inform the client that a CGI script could not be executed.
* Parameter: the client socket descriptor. */
/**********************************************************************/
void cannot_execute(int client)
{
char buf[1024];
//发送500
sprintf(buf, "HTTP/1.0 500 Internal Server Errorrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-type: text/htmlrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<P>Error prohibited CGI execution.rn");
send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Print out an error message with perror() (for system errors; based
* on value of errno, which indicates system call errors) and exit the
* program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{
perror(sc);
exit(1);
}

/**********************************************************************/
/* Execute a CGI script. Will need to set environment variables as
* appropriate.
* Parameters: client socket descriptor
* path to the CGI script */
/**********************************************************************/
//执行cgi动态解析
void execute_cgi(int client, const char *path,
const char *method, const char *query_string)
{
char buf[1024];
int cgi_output[2];//声明的读写管道,切莫被名称给忽悠,会给出图进行说明
int cgi_input[2];//
pid_t pid;
int status;
int i;
char c;
int numchars = 1;
int content_length = -1;

buf[0] = 'A'; buf[1] = '';
if (strcasecmp(method, "GET") == 0)
//如果是GET请求
//读取并且丢弃头信息
while ((numchars > 0) && strcmp("n", buf))
numchars = get_line(client, buf, sizeof(buf));
else
{
//处理的请求为POST
numchars = get_line(client, buf, sizeof(buf));
while ((numchars > 0) && strcmp("n", buf))
{//循环读取头信息找到Content-Length字段的值
buf[15] = '';//目的是为了截取Content-Length:

if (strcasecmp(buf, "Content-Length:") == 0)
//"Content-Length: 15"
content_length = atoi(&(buf[16]));//获取Content-Length的值
numchars = get_line(client, buf, sizeof(buf));
}
if (content_length == -1) {
//错误请求
bad_request(client);
return;
}
}
//返回正确响应码200
sprintf(buf, "HTTP/1.0 200 OKrn");
send(client, buf, strlen(buf), 0);
//#include<unistd.h>
//int pipe(int filedes[2]);
//返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。
//必须在fork()中调用pipe(),否则子进程不会继承文件描述符。
//两个进程不共享祖先进程,就不能使用pipe。但是可以使用命名管道。
//pipe(cgi_output)执行成功后,cgi_output[0]:读通道 cgi_output[1]:写通道,这就是为什么说不要被名称所迷惑
if (pipe(cgi_output) < 0) {
cannot_execute(client);
return;
}
if (pipe(cgi_input) < 0) {
cannot_execute(client);
return;
}

if ( (pid = fork()) < 0 ) {
cannot_execute(client);
return;
}
//fork出一个子进程运行cgi脚本
if (pid == 0) /* 子进程: 运行CGI 脚本 */
{
char meth_env[255];
char query_env[255];
char length_env[255];

dup2(cgi_output[1], 1);//1代表着stdout,0代表着stdin,将系统标准输出重定向为cgi_output[1]
dup2(cgi_input[0], 0);//将系统标准输入重定向为cgi_input[0],这一点非常关键,
//cgi程序中用的是标准输入输出进行交互
close(cgi_output[0]);//关闭了cgi_output中的读通道
close(cgi_input[1]);//关闭了cgi_input中的写通道
//CGI标准需要将请求的方法存储环境变量中,然后和cgi脚本进行交互
//存储REQUEST_METHOD
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == 0) {
//存储QUERY_STRING
sprintf(query_env, "QUERY_STRING=%s", query_string);
putenv(query_env);
}
else { /* POST */
//存储CONTENT_LENGTH
sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
putenv(length_env);
}
// 表头文件#include<unistd.h>
// 定义函数
// int execl(const char * path,const char * arg,....);
// 函数说明
// execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
// 返回值
// 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
execl(path, path, NULL);//执行CGI脚本
exit(0);
} else { /* 父进程 */
close(cgi_output[1]);//关闭了cgi_output中的写通道,注意这是父进程中cgi_output变量和子进程要区分开
close(cgi_input[0]);//关闭了cgi_input中的读通道
if (strcasecmp(method, "POST") == 0)
for (i = 0; i < content_length; i++) {
//开始读取POST中的内容
recv(client, &c, 1, 0);
//将数据发送给cgi脚本
write(cgi_input[1], &c, 1);
}
//读取cgi脚本返回数据
while (read(cgi_output[0], &c, 1) > 0)
//发送给浏览器
send(client, &c, 1, 0);
//运行结束关闭
close(cgi_output[0]);
close(cgi_input[1]);
//定义函数:pid_t waitpid(pid_t pid, int * status, int options);
//函数说明:waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束.
//如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回,
//而子进程的进程识别码也会一快返回.
//如果不在意结束状态值, 则参数status 可以设成NULL. 参数pid 为欲等待的子进程识别码, 其他数值意义如下:
//1、pid<-1 等待进程组识别码为pid 绝对值的任何子进程.
//2、pid=-1 等待任何子进程, 相当于wait().
//3、pid=0 等待进程组识别码与目前进程相同的任何子进程.
//4、pid>0 等待任何子进程识别码为pid 的子进程.
waitpid(pid, &status, 0);
}
}

/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
* carriage return, or a CRLF combination. Terminates the string read
* with a null character. If no newline indicator is found before the
* end of the buffer, the string is terminated with a null. If any of
* the above three line terminators is read, the last character of the
* string will be a linefeed and the string will be terminated with a
* null character.
* Parameters: the socket descriptor
* the buffer to save the data in
* the size of the buffer
* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//解析一行http报文
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '';
int n;

while ((i < size - 1) && (c != 'n'))
{
n = recv(sock, &c, 1, 0);
/* DEBUG printf("%02Xn", c); */
if (n > 0)
{
if (c == 'r')
{
n = recv(sock, &c, 1, MSG_PEEK);
/* DEBUG printf("%02Xn", c); */
if ((n > 0) && (c == 'n'))
recv(sock, &c, 1, 0);
else
c = 'n';
}
buf[i] = c;
i++;
}
else
c = 'n';
}
buf[i] = '';

return(i);
}

/**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on
* the name of the file */
/**********************************************************************/
void headers(int client, const char *filename)
{
char buf[1024];
(void)filename; /* could use filename to determine file type */
//发送HTTP头
strcpy(buf, "HTTP/1.0 200 OKrn");
send(client, buf, strlen(buf), 0);
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/htmlrn");
send(client, buf, strlen(buf), 0);
strcpy(buf, "rn");
send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(int client)
{
char buf[1024];
//返回404
sprintf(buf, "HTTP/1.0 404 NOT FOUNDrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/htmlrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfillrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specifiedrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>rn");
send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* Send a regular file to the client. Use headers, and report
* errors to client if they occur.
* Parameters: a pointer to a file structure produced from the socket
* file descriptor
* the name of the file to serve */
/**********************************************************************/
//将请求的文件发送回浏览器客户端
void serve_file(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = 1;
char buf[1024];

buf[0] = 'A'; buf[1] = '';//这个赋值不清楚是干什么的
while ((numchars > 0) && strcmp("n", buf)) //将HTTP请求头读取并丢弃
numchars = get_line(client, buf, sizeof(buf));
//打开文件
resource = fopen(filename, "r");
if (resource == NULL)
//如果文件不存在,则返回not_found
not_found(client);
else
{
//添加HTTP头
headers(client, filename);
//并发送文件内容
cat(client, resource);
}
fclose(resource);//关闭文件句柄
}

/**********************************************************************/
/* This function starts the process of listening for web connections
* on a specified port. If the port is 0, then dynamically allocate a
* port and modify the original port variable to reflect the actual
* port.
* Parameters: pointer to variable containing the port to connect on
* Returns: the socket */
/**********************************************************************/
//启动服务端
int startup(u_short *port)
{
int httpd = 0;
struct sockaddr_in name;
//设置http socket
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
if (*port == 0) /*动态分配一个端口 */
{
int namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
//监听连接
if (listen(httpd, 5) < 0)
error_die("listen");
return(httpd);
}

/**********************************************************************/
/* Inform the client that the requested web method has not been
* implemented.
* Parameter: the client socket */
/**********************************************************************/
void unimplemented(int client)
{
char buf[1024];
//发送501说明相应方法没有实现
sprintf(buf, "HTTP/1.0 501 Method Not Implementedrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/htmlrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implementedrn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</TITLE></HEAD>rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>HTTP request method not supported.rn");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>rn");
send(client, buf, strlen(buf), 0);
}

/**********************************************************************/

int main(void)
{
int server_sock = -1;
u_short port = 0;
int client_sock = -1;
struct sockaddr_in client_name;
int client_name_len = sizeof(client_name);
pthread_t newthread;
//启动server socket
server_sock = startup(&port);

printf("httpd running on port %dn", port);

while (1)
{
//接受客户端连接
client_sock = accept(server_sock,
(struct sockaddr *)&client_name,
&client_name_len);
if (client_sock == -1)
error_die("accept");
/*启动线程处理新的连接 */
if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
perror("pthread_create");
}
//关闭server socket
close(server_sock);

return(0);
}

tinyhttpd

0x03 soap协议讲解

嵌入式web通信——SOAP

SOAP(Simple Object Accrss Protocol,简单对象访问协议)是一种简单的基于XML的协议,可以使应用程序在分散或分布式的环境中通过HTTP,SMTP等协议来交互信息。

  • XML:一种可扩展标记语言,被设计用来传输和存储数据
  • XML命名空间:用来避免元素命名冲突

eg:

image-20220826170625905

命名空间的定义格式

xmlns:前缀标识=“用于标示命名空间的地址”

eg:

image-20220826170741042

SOAP组成

  • SOAP消息封装
  • SOAP编码规则
  • SOAPRPC表示
  • SOAP绑定

SOAP语法规则

  • SOAP消息必须用XML来编码
  • SOAP消息必须使用SOAP Envelope命名空间
  • SOAP消息必须使用SOAP Encoding Style命名空间
  • SOAP消息不能包含DTD引用
  • SOAP消息不能包含XML处理指令

SOAP消息基本结构

image-20220826171043430

SOAP报文实例

image-20220826171128206

请求

image-20220826171138971

响应

image-20220826171215281

0x04 常见嵌入式web应用及漏洞分析讲解

1. 常用嵌入式web系统

  • Lighttpd:开源轻量、高性能、安全性、兼容性高、适合静态资源
  • Goahead:嵌入式实时操作系统,开源跨平台
  • Shttpd
  • Thttpd
  • Boa
  • Mini_httpd
  • Appweb

2. 常见漏洞类型

2.1 信息泄露

漏洞描述:

敏感信息包括但不限于口令、密钥、证书、会话标识,License,隐私数据,授权凭证,个人数据(如姓名、地址、电话等),在程序文件,配置文件、日志文件、备份文件,固件中都有可能包含敏感信息

检测方法:

  1. 工具,爬虫等扫描获得敏感信息,敏感文件路径
  2. 手工挖掘,根据web同期或网页源代码获得敏感信息

使用firmwalker脚本查找敏感文件路径

github项目地址:https://github.com/craigz28/firmwalker.git

信息泄露案例:Moxa EDR-810

CVE-2019-10963:未经身份验证的攻击者可以从Web服务器检索所有日志文件

image-20220826174322172

image-20220826174349251

2.2 任意文件下载

漏洞描述:

任意文件下载漏洞,正常的利用手段是下载服务器文件,如脚本文件,服务器配置或者是系统配置等等,在能提取固件的情况下能够很好的获得文件路径。

漏洞危害:

下载服务器中任意文件,如脚本代码、服务及系统配置文件等,在这些文件中能获得一些敏感的信息等数据

任意文件下载案例:GL-AR300M-Lite

CVE-2019-6273:任意文件下载

image-20220826174717746

image-20220826174730539

image-20220826174804976

2.3 未授权访问

漏洞描述:

服务器未授权访问可以理解为需要安全配置或权限认证的地址、授权页面存在缺陷,导致其他用户可以直接访问,从而引发重要权限可被操作、网站目录等敏感信息泄露

漏洞危害:

未授权访问能引发重要权限可被操作、任意用户登录、网站目录、用户密码等敏感信息泄露

未授权访问案例:Geovision Inc IP 摄像头

image-20220826175058817

2.4 命令执行

漏洞原理:

命令执行漏洞介绍当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数

漏洞危害:

根据不同用户权限,能执行任意命令,造成的后果十分严重,稍加利用就能获得shell

命令执行案例:D-Link DIR-823G

image-20220826175327065

image-20220826175338621