Rust를 공부해야지 공부해야지 하다가 매우 오랜 시간 동안, 결국은 아무것도 안하고 있길래, 그냥 무작정 만들어보기로 했다.

 

그리고 아래 글을 읽은 이유도 있다.

http://egloos.zum.com/agile/v/5664879

 

프로그래밍 언어 배우기의 달인

미육군에서는 9년간 약 4천만불을 들여 개발한 지뢰 탐지 시스템(Handheld Standoff Mine Detection System, HSTAMIDS 나중에 PSS-14로 개명)을 취소하려고 하고 있었습니다. 프로토타입의 실 테스트에서 지뢰 탐

egloos.zum.com

대충 언어를 새로 배울 때 세가지 원칙을 기준으로 공부를 한다는 점이다.

1. 이 언어로 뭘 만들지를 생각하면서 튜토리얼을 본다.

2. 공부할 때, 표준 라이브러리 코드를 읽는다.

3. 다른 사람이작성한 코드에 내가 필요한 기능을 추가한다.

 

위 글을 보고 영감을 받아서, 그냥 무작정 지루한 챕터들을 읽기 보다는 바로 무언가를 만들어 보는게 낫지 않나 싶다.

 

 

뭘 만들면 좋을까 하다가, 간단한 웹 서버를 만들어 보기로 했다. 어차피 간단한 웹서버는 정말 간단하고, 기능들을 추가하고자 한다면 계속해서 추가할 수 있으니 말이다.

 

Rust 개발환경 구축은 다음 링크 글에서 확인해볼 수 있다.

https://eine.tistory.com/entry/Rust-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95

 

Rust 개발 환경 구축(Windows10에서 Sublime Text 3 / VS code)

느닷없이 Rust를 배우고 싶어져서 Rust 개발환경을 한번 구축해보도록 하겠습니다. 사실 지금 글을 쓰는 이 시점에는 Rust를 공식적으로 지원하는 IDE나 그런 녀석들은 잘 없는데, 그래도 제일 많이

eine.tistory.com

 

일단 개발 환경은, 내가 주로 윈도우 PC를 사용하므로 아래와 같을 예정이다.

개발환경

 - Windows 10

 - rustc 1.44.0-nightly (f509b26a7 2020-03-18)

 - cargo 1.44.0-nightly (7019b3ed3 2020-03-17)

 

일단 github 레포지토리와 개발할 코드 디렉토리를 만들어보자.

 

 

그리고 repository를 clone할 디렉토리에서 WSL win bash를 켠 뒤, git clone을 해준다.

 

 

Rust 프로젝트 생성

이제 rust project를 카고로 만들어줘야 하는데, cargo new rust_web_server --bin 이라는 명령어로 프로젝트 디렉토리를 만들 수 있다.

 

 

근데 안에 디렉토리가 또 생겨버렸다. 이미 README.md와 .git이 있는 디렉토리므로 저 명령어 말고, 다른 명령어를 써야 하는 것 같다.

cargo init --bin이라고 치니 되는 것 같다.

 

 

디렉토리 구조를 보니 바람직하게 만들어진 듯 하다.

일단 VScode로 해당 디렉토리를 열어서 Hello world가 잘 동작하는지 확인해보자.

 

 

기본적으로 소스코드에 hello world프로그램이 작성되어 있다.

아래 터미널을 열어서 cargo build와 cargo run을 쳐 보니, Hello, world!가 잘 되었다.

 

네트워크 기능부터

일단 웹 서버이니 TCP 소캣을 열 수 있어야 한다. Rust에서 소캣 프로그래밍을 어떻게 하는지를 한번 알아보자.

 

소캣 프로그래밍

https://doc.rust-lang.org/std/net/struct.TcpListener.html

 

std::net::TcpListener - Rust

pub struct TcpListener(_); A TCP socket server, listening for connections. After creating a TcpListener by binding it to a socket address, it listens for incoming TCP connections. These can be accepted by calling accept or by iterating over the Incoming it

doc.rust-lang.org

TcpListener라는 녀석이 있다. 일단 소캣을 하나 열어서 Listening하는 예제 코드가 있으니 한번 테스트 해보자.

 

use std::net::{TcpListener, TcpStream};

fn handle_client(stream: TcpStream) {
    println!("Connection!")
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:80")?;

    // accept connections and process them serially
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

접속이 되면 stdout에 Connection!이라고 출력만 된다.

일단 여기까지 하고 initial commit을 한번 해 놓겠다.

 

이제 Tcp로 데이터를 전송을 해보고싶은데, Docs랑 예시 코드를 좀 보겠다.

https://riptutorial.com/rust/example/4404/a-simple-tcp-client-and-server-application--echo

 

Rust - A simple TCP client and server application: echo | rust Tutorial

rust documentation: A simple TCP client and server application: echo

riptutorial.com

https://doc.rust-lang.org/std/net/struct.TcpStream.html

 

std::net::TcpStream - Rust

A TCP stream between a local and a remote socket. After creating a TcpStream by either connecting to a remote host or accepting a connection on a TcpListener, data can be transmitted by reading and writing to it. The connection will be closed when the valu

doc.rust-lang.org

stream.write라고 하면 될 것 같은데, 사이에 &는 뭐고 ?는 또 뭔지 모르겠다.

 

찾아보니, &는 burrow라고 해서 인자를 넘겨줄 때, C++의 Reference variable 처럼 Call by reference로 넘기도록 하는 것이고, ?는 Result<T>를 리턴하는 값을 unwrap을 자동적으로 해주도록 하는 매크로이다.

 

 

웹 브라우저로 요청을 보내보니, Response는 안오긴 하는데 Connection이라고 찍히긴 한다.

 

 

그래서 대충 static한 HTTP Response를 찍도록 조금 변경해 보았다.

use std::net::{TcpListener, TcpStream};
use std::io::{Write};

fn handle_client(mut stream: TcpStream) {
    println!("Connection!");
    let data = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
    stream.write(data.as_bytes());
    
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:80")?;

    // accept connections and process them serially
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

그래서 브라우저로 접속을 해볼려고 했는데, 여러번 하면 한번정도 성공하는듯?? 왜 되었다 안되었다 하는지 모르겠다. 

성공하는 확률은 10%정도로 뭔가 Tcp 연결을 잘 못 짠거같긴 하다.

 

그리고 빌드할때마다 다음 warning이 뜬다.

 

 

에러 핸들링을 제대로 안해서 그런 것 같다.

아래와 같이 코드를 바꾸니 warning은 안뜨긴 한다만, 여전히 응답은 제대로 안된다.

use std::net::{TcpListener, TcpStream};
use std::io::{Write};

fn handle_client(mut stream: TcpStream) {
    println!("Connection!");
    let data = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
    stream.write(data.as_bytes()).expect("Response error");
    
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:80")?;

    // accept connections and process them serially
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

근데 약간 이상한것은, Ubuntu 16.04에서 Firefox로 접속하니, hello world!가 잘 찍힌다.

왜 Windows 10에서 크롬으로 접속을 하면 제대로 안되는 것일까..?

 

나중에 확인해보니, tcpstream 마지막에 flush를 해 주니, chrome이든 firefox든 둘 다 잘 돌아간다.

대충 아래와 같은 코드로 고쳐보았다. /로 요청할때와 그 외의 경로로 요청할때의 응답이 다르다.

from_utf8_lossy는 u8 byte stream 값을 readable한 문자열로 변경해주는 함수이다. lossy는 유효하지 않은 UTF-8 배열을 만났을때 어떻게 처리할 것인가에 대한 내용이다. 유효하지 않은 배열 값은 U+FFFD Replacement character라는 값으로 바꾸게 된다.

use std::net::{TcpListener, TcpStream};
use std::io::{Write};
use std::io::prelude::*;

fn handle_client(mut stream: TcpStream) {
    //get request and dump
    let mut request = [0; 1024];
    stream.read(&mut request).unwrap();
    println!("Received HTTP Request...\n{}", String::from_utf8_lossy(&request[..]));
    let response;
    if request.starts_with(b"GET / HTTP/1.1\r\n") {
        //send hello world response
        response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
    } else {
        response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 4\r\n\r\nNope\r\n";
    }
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();

}

fn main() -> std::io::Result<()> {
    let server = "127.0.0.1:1024";
    let listener = TcpListener::bind(server)?;

    // accept connections and process them serially
    println!("Server is running on {}...", server);
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

 

서문

인문학을 전공한 친구의 요청으로, 코딩을 전혀 접해보지 않은 사람을 대상으로 한 "코딩이란 무엇일까?"라는 주제로 포스팅을 해보게 되었다.
이해하기 쉬운 글을 쓰기 위해서, 필자가 쓴 글이 다소 엄밀한 정의와는 거리가 있을 수 있고 예시나 비유를 많이 들 수 있지만, 사전적인 정의보다는 전체적인 흐름을 이해하는데 초점을 맞추어서 글을 읽어주기를 바란다.

코딩이뭔데?

코딩은 영어로 coding인데, 부호를 나타내는 code라는 단어 뒤에 ing가 들어가게 된다. 사전적인 의미로 code는 부호라는 뜻이고, ing은 이 부호를 작성하는 것, 혹은 부호로 바꾸는 것 이라는 뜻이 된다.

여기서 코드(부호)라는 것은 컴퓨터가 처리할 수 있는 명령어라고 보면 된다. 크게 소스코드(source code)와 머신코드(machine code)로 나눌 수 있는데, 이 부분은 자세히는 다루지 않도록 하겠다.

그래서 코딩이란 컴퓨터에게 일을 시키기 위한 코드를 작성하는 것이라고 볼 수 있다.

그러면 프로그래밍은?

프로그래밍은 프로그램을 만드는 것을 프로그래밍이라고 한다. 프로그램은 코드의 모음집이다. 우리가 흔히 쓰는 윈도우 10같은 운영체제도 프로그램이고, 인터넷 익스플로러나 크롬 같은 웹 브라우저도 프로그램이다.
이런 프로그램을 작성하는 것이 프로그래밍이다.

코딩이랑 사실 큰 뜻의 차이는 없고, 코딩은 프로그래밍보다는 좀더 가볍게 적은 양의 코드를 작성한다는 뉘앙스의 차이가 있을 뿐이다.

그러면 코딩을 배우면 무엇을 할 수 있나?

코딩이 컴퓨터에 명령을 내리는 코드를 작성하는 것이니, 당연 코딩을 할 수 있으면 컴퓨터에 명령을 내릴 수 있다.

코딩을 몰라도 명령을 내릴 수 있는데?

맞다 우리는 이미 기본적인 컴퓨터를 사용할 줄 안다. 전원버튼을 눌러서 컴퓨터의 전원을 킬 수 있고, 웹 브라우저를 실행해서 웹 서핑을 할 수 있다. 이미 우리는 코딩을 할 줄 모르지만, 컴퓨터를 사용할 수 있다.

하지만 우리가 이렇게 컴퓨터를 사용하는 것은, 이미 잘 만들어진 다양하고 유용한 컴퓨터 프로그램들이 잘 존재하기 때문이다.

장부같은것을 관리하기 위해서는 엑셀과 같은 스프레드시트 프로그램을 이용하고, 발표자료를 만들기 위해서는 파워포인트같은 프레젠테이션 프로그램을 이용한다. 문서작업을 하기 위해서는 워드와 같은 워드프로세서 프로그램을 이용한다.

그래서 코딩이 왜 유용한건데?

윗 단락에서 컴퓨터로 할 수 있는 다양한 작업들의 예시를 보았다. 그런데 한번 상상해보자.
만약 내가 컴퓨터를 통해서 하려는 작업과 부합하는 프로그램이 없다면? 아니면 가능은 하지만 매우 비효율적으로 해야한다면?

비효율적인 반복 작업을 자동화

예를 하나 들어보자. 당신은 어떤 기업의 행정직으로 입사를 하게 되었다. 당신이 해야 할 일은 회사 전산 시스템에 접속해서 최근 5개년치 회계장부 파일을 모조리 프린트 해야 한다. 그런데 이 회계 장부 파일의 개수가 매우 많아서 하나하나 전산시스템에서 찾아내서 다운로드 받은 뒤, 인쇄를 하려니 단순반복 작업을 매우 많이 해야 한다.

이럴 때, 당신이 코딩을 할 수 있다면 이러한 반복작업들을 처리해주는 프로그램을 작성한 뒤, 이 프로그램이 일을 대신 하도록 맡기고, 당신은 커피한잔의 여유를 가지면서 쉴 수 있다.

실제로 이러한 사례들은 꽤 있으며, 그 중 하나 화제가 되었던 사건이 있다. 아래 유투브 링크를 클릭해서 사례를 참고해보도록 하자.

https://youtu.be/_-ugbwhhApI



 내가 하려는 작업을 해줄 수 있는 프로그램이 없다면?

요즘에는 사람들이 쓸만한 기능에 대한 프로그램들이 꽤나 많이 공유되어 있는 편이다. 하지만 최신기술에 대한 프로그램이나, 특수한 상황에서만 쓰이는 프로그램 등은 누군가가 만들어놓은 경우가 없을 수 있는데 이러한 경우 프로그래밍 능력이 있는 사람에게 돈을 지불하고 외주를 주거나, 직접 해당 프로그램을 작성하거나 해야 한다. 이러한 경우에도 코딩 능력이 있다면 직접 프로그램을 작성할 수 있게 된다

기타 사례들

만약 본인이 유럽여행을 다녀오면서 수 많은 사진을 찍었는데, 이 사진들의 이름을 적절히 변경해서 관리하고 싶을 수 있다. 수천장의 사진의 이름을 일일히 변경할수도 있지만, 사진들을 찍은 시간 순서대로 정렬해서 일정 범위의 사진의 이름 앞에 찍은 도시이름을 포함해서 변경하고 싶을 수 있다. 예컨데 IMG_001 부터 IMG_100까지는 파리_001부터 파리_100 와 같은 식으로 말이다. 이런 경우 파이썬 이나 윈도우 배치 파일을 작성해서 파일명을 한꺼번에 바꾸거나, 다크네이머같은 프로그램을 이용해서 변경할 수 도 있다.

이와 같이 코딩능력이 있거나, 각기 필요한 상황에 쓸 수 있는 프로그램을 안다면 유용하게 사용하여 작업시간들을 단축할 수 있다

개념

Security by obscurity 또는 Security through obscurity라고 부르는 이 녀석은, 어떤 시스템이나 컴포넌트의 보안성을 설계나 구현을 알려지지 않게 하는것에 의존하는 것을 뜻한다.

이 방식만을 이용해서 보안을 적용하는것은 추천되지 않고 있다.

 

내부 구조가 어떻게 생겨먹었는지 알 수 없게 하는 것으로 보안성을 만든다라는 것인데, 약간 말이 이상할 수 있으니 예를 들어보자.

 

예시

 

어떤 컴퓨터가 공공장소에 놓여져 있다. 어떤 사람이 그 컴퓨터를 무단으로 사용하려고(공격하려고)하는데, 버튼을 몇개 눌러보니 윈도우 컴퓨터임을 알 수 있고, 그 윈도우 버전에서 비밀번호 없이 로그인을 할 수 있는 방법이 있음을 알아서 그를 이용해서 로그인을 해서 안에 저장된 자료들을 무단으로 사용하고 온갖 것들을 할 수 있다.

 

그런데 만약 해당 컴퓨터에 Mac OS가 설치되어 있었고, 공격자는 Mac OS를 사용해본 적이 없었다. 그래서 공격을 할 수 없었다고 해보자.

 

공격자는 Mac OS에 대한 이해가 부족하기 때문에 공격에 실패했다. 이러한 방식의 보안을 Security by obscurity라고 볼 수 있다.

 

물론 여기서 아니 누가 Mac OS를 몰라?라고 생각할 수 있는데 그러면 AIX 운영체제나 더 오래된 옛날 Unix 운영체제, Dos 운영체제 등의 예시를 들어보자.

 

잘 안쓰이고 오래되어 잊혀진 구조를 갖는것으로 보안성을 획득하는 것이라고 보면된다.

 

다른 예시를 들어보면, 어떤 파일을 압축을 해서 저장을 한다고 해 보자. 널리 쓰이는 deflate 알고리즘이나 PK zip, gunzip 등의 압축 포맷으로 압축을 하면, 해당 포맷에 대한 압축해제를 하는 프로그램과 알고리즘들이 잘 공개되어 있어서 쉽게 압축을 풀 수 있다. 하지만 만약 공개되지 않은 새로운 압축 알고리즘, 자신들만 쓰는 압축 알고리즘으로 압축을 해 놓으면 이것이 암호화가 아니지만, 그 알고리즘을 모르기 때문에 압축을 풀 수 없다.

이 압축 알고리즘을 알려지지 않도록(비밀로 유지)해서 보안을 성취하는 것 역시 security by obscurity라고 볼 수 있다.

 

Embedded 장비에서 쓰는 파일시스템 중 일부 파일시스템의 Meta data format을 조금 바꾸어서, 일반적인 binwalk와 같은 firmware extractor로 정상적으로 추출되지 않도록 하는 방식도 security by obscurity라고 볼 수 있다.

[코드엔진 2018년도 공유기 펌웨어 커스터마이징 관련 발표자료]

(https://github.com/codeengn/codeengn-conference/blob/master/15/2018%20CodeEngn%20Conference%2015%2C%20%EA%B3%B5%EC%9C%A0%EA%B8%B0%20%EC%BB%A4%EC%8A%A4%ED%85%80%20%ED%8E%8C%EC%9B%A8%EC%96%B4%20%EA%B0%9C%EB%B0%9C%EC%9D%98%20%EC%9D%B4%ED%95%B4%20%5B%EA%B9%80%EC%8A%B9%EC%A3%BC%5D.pdf)

 

Google CTF 2018 - Beginner's Quest (Security by obscurity)

https://ctftime.org/task/6260

 

CTFtime.org / Google Capture The Flag 2018 (Quals) / Beginner's Quest - Security by obscurity

 

ctftime.org

2018년도 Google CTF 문제 중, Beginner's Quest 문제 셋에보면, Security by obscurity 문제가 있다.

 

압축만 되어 있는데, 압축 포맷이 다양하게 되어 있어서 각각의 압축 포맷에 대한 압축 해제를 할 수 없다면 문제를 풀어낼 수 없는 식으로 되어 있다.

 

압축에 비밀 키에 보안 의존성이 있는 것이 아닌, 압축 포맷 자체에 보안 의존성이 있는 것이다.

역사

<wiki의 history 탭을 번역을 해 보았는데 좀 이상합니다. 불완전한 내용이므로 접은 글 처리합니다.>

더보기

Security by obscurity는 자물쇠공 알프래드 찰스 홉스라는 사람을 상대하기 위해 사용되었었다. 찰스 홉스는 1851년에 대중들에게 최첨단 자물쇠도 쉽게 따버릴 수 있다는것을 몸소 보여준 사람이다. "자물쇠 설계의 보안 결합을 노출하면 범죄자에게 더욱더 취약해질 수 있다"라는 우려에 대해서 그는, "도둑들은 해당 부분에 대해 매우 열심히며, 우리가 알고 있는 것 보다 이미 더 많은 것들을 알고 있다"라고 말했다.

 

Security through obscurity에 대한 공식적인 문헌은 매우 부족하다. 예를들어서 보안공학에 관한 책은 1883년 부터 kerckhoffs의 교리를 계속 인용하는데, 예를 들어서 핵 명령 통제를 비밀로 해야 할지 개방해야 할지에 대한 토론에서:

 

우발적인 전쟁의 가능성을 줄이는 것이 핵 명령 통제 기술을 비밀로 두는 것 보다 더 중요하다. 이는 Kerchkhoffs의 교리의 현대적인 재해석에 해당하며, 보안 시스템은 구조를 모호하게 두는 방식이 아닌 키에 의존해야 한다. 라고 말했다.

(의역을 해보자면, 핵 기술을 비밀로 두는 것 보다, 전쟁의 가능성이 더 중요하다는 것인데, 핵 기술을 공개를 하면 서로 핵을 갖기 때문에 서로 조심하게 되어 전쟁의 가능성이 줄어든다 그런 이야기를 하는 것 같아요)

 

법학분야에서 Peter Swire는 "모호함을 통한 보안은 환상", "느슨한 입술이 배를 가라 앉히는" 군사 개념과, 어떻게 경쟁이 인센티브를 공개하게 만드는지에 대한 상충관계에 대한 글을 썼다. (security by obscurity가 구현한 내용들을 비밀로 두는 것으로 보안을 성취하는 것인데, 공개 경쟁을 하기 위해서는 이러한 구현 내용들을 공개해야 한다는 것. 즉 security by obscurity에 반대하는 내용인 것 같습니다.)

비판

Security by obscurity만 가지고 보안성을 담보하는 것은 추천되지 않고, 표준정책에도 좋지 않습니다. 미국의 NIST에서는 이 방식의 보안에 대해 반대되는 의견을 내고 있으며 이는, "시스템 보안은 구현이나 컴포넌트의 비밀성에 의존하면 안됩니다."라는 문구로 대변됩니다.

이 방식의 보안은, security by design(설계에 의한 보안)과 open security(공개 보안)과 대조지만, 실제 프로젝트들은 이러한 전략들을 모두 사용하곤 합니다.

Reference

https://en.wikipedia.org/wiki/Security_through_obscurity

+ Recent posts