代碼含有詳細註釋, 不詳解
0x01 測試頁面的準備
首先編寫一個測試頁面, 我這裏使用的PHP
如果有其它環境測試的話,可以直接從步驟2開始看
測試代碼片段
<?php
if(isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD'])=='POST'){
echo "POST Success re=1 \n";
$data = file_get_contents("php://input");
echo "data is : ==>> ";
print_r($data);
}else{
echo "Not is POST re=0 \n";
}
?>
上面代碼片段的含義就是,當訪問該網頁是以POST方式提交的話,那麼就將提交上來的結果返回給客戶端。如果不是,則打印 Not is POST re=0
如果自己沒有測試頁面,也沒有web後端開發能力,可以使用我的測試頁面,但不保證永久提供,在可測試期間,如果遇到問題,歡迎留言,測試地址如下
https://www.wangsansan.com/mydir/test/HttpsPostTest.php
0x02 C語言程序
本程序依賴 openssl 如果系統沒有相關庫,請自行安裝,此文不做闡述
1、https_post.h
/************************************************************************
> File Name: https_post.h
> Author: WangMinghang
> Mail: [email protected]
> Blog: https://www.wangsansan.com
> Created Time: 2018年08月29日 星期三 16時42分21秒
***********************************************************************/
#ifndef __HTTPS_POST__
#define __HTTPS_POST__
/*
* @Name - HTTPS的POST提交
* @Parame *host - 主機地址, 即域名
* @Parame port - 端口號, 一般爲443
* @Parame *url - url相對路徑
* @Parame *data - 要提交的數據內容, 不包括Headers
* @Parame dsize - 需要發送的數據包大小, 由外部調用傳入, 不包含頭
* @Parame *buff - 數據緩存指針, 非空數組或提前malloc
* @Parame bsize - 需要讀取的返回結果長度, 可以儘量給大, 直到讀取結束
*
* @return - 返回結果長度, 如果讀取失敗, 則返回值 <0
* -1 : 爲POST數據申請內存失敗
* -2 : 建立TCP連接失敗
* -3 : SSL初始化或綁定sockfd到SSL失敗
* -4 : POST提交失敗
* -5 : 等待響應失敗
*/
int https_post(char *host, int port, char *url, const char *data, int dsize, char *buff, int bsize);
#endif
2、https_post.c
/************************************************************************
> File Name: https_post.c
> Author: WangMinghang
> Mail: [email protected]
> Blog: https://www.wangsansan.com
> Created Time: 2018年08月29日 星期三 16時42分21秒
***********************************************************************/
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
//#include "https_post.h"
#define HTTP_HEADERS_MAXLEN 512 // Headers 的最大長度
/*
* Headers 按需更改
*/
const char *HttpsPostHeaders = "User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"
"Cache-Control: no-cache\r\n"
"Accept: */*\r\n"
"Content-type: application/json\r\n";
/*
* @Name - 創建TCP連接, 並建立到連接
* @Parame *server - 字符串, 要連接的服務器地址, 可以爲域名, 也可以爲IP地址
* @Parame port - 端口
*
* @return - 返回對應sock操作句柄, 用於控制後續通信
*/
int client_connect_tcp(char *server,int port)
{
int sockfd;
struct hostent *host;
struct sockaddr_in cliaddr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
perror("create socket error");
return -1;
}
if(!(host=gethostbyname(server))){
printf("gethostbyname(%s) error!\n", server);
return -2;
}
bzero(&cliaddr,sizeof(struct sockaddr));
cliaddr.sin_family=AF_INET;
cliaddr.sin_port=htons(port);
cliaddr.sin_addr=*((struct in_addr *)host->h_addr);
if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(struct sockaddr))<0){
perror("[-] error");
return -3;
}
return(sockfd);
}
/*
* @Name - 封裝post數據包括headers
* @parame *host - 主機地址, 域名
* @parame port - 端口號
* @parame page - url相對路徑
* @parame len - 數據內容的長度
* @parame content - 數據內容
* @parame data - 得到封裝的數據結果
*
* @return int - 返回封裝得到的數據長度
*/
int post_pack(const char *host, int port, const char *page, int len, const char *content, char *data)
{
int re_len = strlen(page) + strlen(host) + strlen(HttpsPostHeaders) + len + HTTP_HEADERS_MAXLEN;
char *post = NULL;
post = malloc(re_len);
if(post == NULL){
return -1;
}
sprintf(post, "POST %s HTTP/1.0\r\n", page);
sprintf(post, "%sHost: %s:%d\r\n",post, host, port);
sprintf(post, "%s%s", post, HttpsPostHeaders);
sprintf(post, "%sContent-Length: %d\r\n\r\n", post, len);
sprintf(post, "%s%s", post, content); // 此處需要修改, 當業務需要上傳非字符串數據的時候, 會造成數據傳輸丟失或失敗
re_len = strlen(post);
memset(data, 0, re_len+1);
memcpy(data, post, re_len);
free(post);
return re_len;
}
/*
* @Name - 初始化SSL, 並且綁定sockfd到SSL
* 此作用主要目的是通過SSL來操作sock
*
* @return - 返回已完成初始化並綁定對應sockfd的SSL指針
*/
SSL *ssl_init(int sockfd)
{
int re = 0;
SSL *ssl;
SSL_CTX *ctx;
SSL_library_init();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL){
return NULL;
}
ssl = SSL_new(ctx);
if (ssl == NULL){
return NULL;
}
/* 把socket和SSL關聯 */
re = SSL_set_fd(ssl, sockfd);
if (re == 0){
SSL_free(ssl);
return NULL;
}
/*
* 經查閱, WIN32的系統下, 不能很有效的產生隨機數, 此處增加隨機數種子
*/
RAND_poll();
while (RAND_status() == 0)
{
unsigned short rand_ret = rand() % 65536;
RAND_seed(&rand_ret, sizeof(rand_ret));
}
/*
* ctx使用完成, 進行釋放
*/
SSL_CTX_free(ctx);
return ssl;
}
/*
* @Name - 通過SSL建立連接併發送數據
* @Parame *ssl - SSL指針, 已經完成初始化並綁定了對應sock句柄的SSL指針
* @Parame *data - 準備發送數據的指針地址
* @Parame size - 準備發送的數據長度
*
* @return - 返回發送完成的數據長度, 如果發送失敗, 返回 -1
*/
int ssl_send(SSL *ssl, const char *data, int size)
{
int re = 0;
int count = 0;
re = SSL_connect(ssl);
if(re != 1){
return -1;
}
while(count < size)
{
re = SSL_write(ssl, data+count, size-count);
if(re == -1){
return -2;
}
count += re;
}
return count;
}
/*
* @Name - SSL接收數據, 需要已經建立連接
* @Parame *ssl - SSL指針, 已經完成初始化並綁定了對應sock句柄的SSL指針
* @Parame *buff - 接收數據的緩衝區, 非空指針
* @Parame size - 準備接收的數據長度
*
* @return - 返回接收到的數據長度, 如果接收失敗, 返回值 <0
*/
int ssl_recv(SSL *ssl, char *buff, int size)
{
int i = 0; // 讀取數據取換行數量, 即判斷headers是否結束
int re;
int len = 0;
char headers[HTTP_HEADERS_MAXLEN];
if(ssl == NULL){
return -1;
}
// Headers以換行結束, 此處判斷頭是否傳輸完成
while((len = SSL_read(ssl, headers, 1)) == 1)
{
if(i < 4){
if(headers[0] == '\r' || headers[0] == '\n'){
i++;
if(i>=4){
break;
}
}else{
i = 0;
}
}
//printf("%c", headers[0]); // 打印Headers
}
len = SSL_read(ssl, buff, size);
return len;
}
int https_post(char *host, int port, char *url, const char *data, int dsize, char *buff, int bsize)
{
SSL *ssl;
int re = 0;
int sockfd;
int data_len = 0;
int ssize = dsize + HTTP_HEADERS_MAXLEN; // 欲發送的數據包大小
char *sdata = malloc(ssize);
if(sdata == NULL){
return -1;
}
// 1、建立TCP連接
sockfd = client_connect_tcp(host, port);
if(sockfd < 0){
free(sdata);
return -2;
}
// 2、SSL初始化, 關聯Socket到SSL
ssl = ssl_init(sockfd);
if(ssl == NULL){
free(sdata);
close(sockfd);
return -3;
}
// 3、組合POST數據
data_len = post_pack(host, port, url, dsize, data, sdata);
// 4、通過SSL發送數據
re = ssl_send(ssl, sdata, data_len);
if(re < 0){
free(sdata);
close(sockfd);
SSL_shutdown(ssl);
return -4;
}
// 5、取回數據
int r_len = 0;
r_len = ssl_recv(ssl, buff, bsize);
if(r_len < 0){
free(sdata);
close(sockfd);
SSL_shutdown(ssl);
return -5;
}
// 6、關閉會話, 釋放內存
free(sdata);
close(sockfd);
SSL_shutdown(ssl);
ERR_free_strings();
return r_len;
}
其中在 ssl_init() 函數中,包含一片段置隨機數種子的代碼,對此片段函數釋義以下是查閱得到的結果
在win32 的環境中client程序運行時出錯(SSL_connect返回-1)的一個主要機制便是與UNIX平臺下的隨機數生成機制不同(握手的時候用的到). 具體描述可見mod_ssl的FAQ.解決辦法就是調用此函數,其中buf應該爲一隨機的字符串,作爲"seed".
此文件內包含詳細註釋,就不過多闡述
其中,含有1個已知bug,在代碼98行,當程序需要上傳非字符串時,則不能使用sprintf()進行拼接,使用是按需修改
下面編寫一個測試程序
3、example.c
/************************************************************************
> File Name: https_post_test.c
> Author: WangMinghang
> Mail: [email protected]
> Blog: https://www.wangsansan.com
> Created Time: 2018年08月29日 星期三 17時36分06秒
***********************************************************************/
#include <stdio.h>
#include <string.h>
#include "https_post.h"
int Port = 443;
char *Host = "www.wangsansan.com";
char *Page = "/test/HttpsPostTest.php";
char *Data = "{\"A\":\"111\", \"B\":\"222\"}"; // 對應字符串 - {"A":"111", "B":"222"}
int main()
{
int read_len = 0;
char buff[512] = {0};
read_len = https_post(Host, Port, Page, Data, strlen(Data), buff, 512);
if(read_len < 0){
printf("Err = %d \n", read_len);
return read_len;
}
printf("==================== Recv [%d] ==================== \n", read_len);
printf("%s\n", buff);
return 1;
}
0x03 編譯測試
依賴於ssl和crypto庫,編譯的時候使用-l鏈接
以下是編譯過程以及測試結果
~$ gcc https_post.c example.c -o https_post -lssl -lcrypto
~$ ./https_post
==================== Recv [56] ====================
POST Success re=1
data is : ==>> {"A":"111", "B":"222"}
~$
CSDN:http://blog.csdn.net/byb123
Blog:https://www.wangsansan.com/
公衆號:iamwangsansan (山中書)
歡迎關注
不定時更新