Git Product home page Git Product logo

clammit's Introduction

Clammit

Build Status Code Climate

Clammit is an HTTP interface to the ClamAV virus scanner, able to unwrap HTTP bodies, checking them against clamd and returning a binary clean/virus status.

Usage

Clammit parses and processes inbound HTTP requests. When it handles a request whose Content-Length is non-zero, it will attempt to decode multipart file uploads and pass each part or the whole body to ClamAV. If ClamAV detects a virus, clammit will then return a response with code 418 to the caller. Otherwise, it will continue processing.

Clammit can be be used in two ways: as an intercepting proxy or as a virus check service.

Usage as a proxy

When used as a proxy, clammit sits in between the client and your application, thus preventing uploads with virus files to reach your application, by returning a 418 to your app's client. For this mode to work, clammit must be able to contact your app directly, so it is best suited when clammit is executing on the same machine as your app.

As an example, say you have a foo Rails application that is configured in Nginx like this:

server {
  listen 80;
  server_name foobar.example.com;

  root /home/foobar/public;
  try_files $uri/index.html $uri @foobar;

  location @foobar {
    access_log /var/log/nginx/foobar.app-access.log;
    error_log  /var/log/nginx/foobar.app-error.log;

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://unix:/home/foobar/.unicorn.sock;
  }
}

Assuming you receive document uploads at POST /documents, to check them with a Clammit that has been configured to listen on an UNIX socket in /var/run/clammit.sock, you should add a location block like this:

  location /documents {
    access_log /var/log/nginx/foobar.clammit-access.log;
    error_log  /var/log/nginx/foobar.clammit-error.log;

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # This is where clammit will forward requests that do not contain viruses
    # or that did not contain any data to scan.
    proxy_set_header X-Clammit-Backend unix:/home/foobar/.unicorn.sock;

    proxy_pass http://unix:/var/run/clammit.sock;
  }

All requests to /documents will then pass through Clammit, and uploads will be scanned for viruses. To your application the request will just appear as coming from Nginx, all cookies and the headers will be kept intact.

If a virus is detected, Clammit will reject the request (with a 418 status code), and not forward it to your application. If you use an AJAX uploader, you can interpret this response and show a nice error message to end users. Or you could set a custom error page in Nginx.

Usage as a service

When used as a service, clammit can be anywhere in your architecture, and it will return a 200 OK if the request has no virus, or a configurbale status code if a virus is detected.

To scan a file, send it via HTTP - using any method you prefer - to the /clammit/scan endpoint.

Example, with cURL:

curl -sf http://localhost:8438/clammit/scan -d @/some/file

Or with Python:

import requests

>>> r = requests.post('http://localhost:8438/clammit/scan', files={'file': open('/etc/passwd', 'rb')})
>>> r.status_code
200

>>> r = requests.post('http://localhost:8438/clammit/scan', files={'file': b'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'})
>>> r.status_code
418

Or with Ruby:

require 'httparty'

>> r = HTTParty.post('http://localhost:8438/clammit/scan', body: { file: File.open('/etc/passwd') })
>> r.code
=> 200

>> r = HTTParty.post('http://localhost:8438/clammit/scan', multipart: true, body: { file: 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' })
>> r.code
=> 418

Configuration

You will need to create and edit a configuration file. An example is found in etc.sample/

The configuration is pretty simple:

[ application ]
listen          = :8438
application-url = http://host:port/path
clamd-url       = http://host:port/
log-file        = /var/log/clammit.log
debug           = true
test-pages      = true
Setting Description
listen The listen address (see below)
unix-socket-perms The file mode of the UNIX socket, if listening on one
clamd-url The URL of the clamd server
virus-status-code (Optional) The HTTP status code to return when a virus is found. Default 418
application-url (Optional) Forward all requests to this application
content-memory-threshold (Optional) Maximum payload size to keep in RAM. Larger files are spooled to disk
log-file (Optional) The clammit log file, if ommitted will log to stdout
test-pages (Optional) If true, clammit will also offer up a page to perform test uploads
debug (Optional) If true, more things will be logged

The listen address can be a TCP port or Unix socket, e.g.:

  • 0.0.0.0:8438 - Listen on all IPs on port 8438
  • unix:/var/run/clammit.sock - Listen on a Unix socket

The same format applies to the clamd-url and application-url parameters.

By default Clammit will look for a X-Clammit-Backend header, and use that to decide where to send requests to. If you only have one backend server, you can set it in the application-url configuration option, and omit the header.

Architecture

Flow-wise, Clammit is straightforward. It sets up an HTTP server to accept incoming requests (main.go):

  1. Each request is passed to the forwarder (forwarder/forwarder.go)
  2. The forwarder downloads the request body (as it will be used at least twice)
  3. The forwarder passes the request to the clam interceptor (clam_interceptor.go)
  4. The only request that will be tested will have methods POST/PUT/PATCH
  5. The clam interceptor locates and sends each form-data field to ClamD
  6. For any positive response, the interceptor will write an HTTP response and return (and the forwarder will not attempt to forward the request)
  7. If the interceptor OKs the request, the forwarder constructs a new HTTP request and forwards to the application
  8. The application's response is returned as the response to the original request

Building

Clammit is requires the Go compiler, version 1.15 or above. It also requires make to ease compilation. The makefile is pretty simple, though, so you can perform its steps manually if you want.

You will need external access to github and code.google.com to load the third-party packages that Clammit depends on: go-clamd and gcfg.

Once you have this, simple run:

make

This will download the third-party packages and compile Clammit. The resulting binary is found in the bin directory.

Other make options are:

Option Description
make clean Removes the compiled binary and intermediate libraries (in pkg/)
make cleanimports Removes the downloaded third-party source packages
make gets Downloads the third-party source packages, if they are not already there
make test Runs the application unit tests

Installation

  1. Copy the compiled binary (bin/clammit), either into your project repository, or to an installation area.
  2. Edit the configuration file as appropriate.
  3. Configure your auto-start mechanism, be it God or init.d. An example systemd unig is provided.
  4. Configure the upstream webserver to forward appropriate POST requests to clammit.

API

Clammit's own actions are grouped under the "/clammit" path. You should ensure that these are not available externally.

Info

  GET /clammit

This method will return JSON giving the current status of Clammit and its connection to ClamAV.

Scan

  POST /clammit/scan

This is the endpoint to submit files for scanning only. Any files to be scanned should be attached as file objects. Clammit will return an HTTP status code of 200 if the request is clean and 418 if there is a bad attachment.

Ready

  GET /clammit/readyz

Returns 200 OK unless we are shutting down, waiting for currently running requests to complete.

Clammit does not implement a liveness check, as clammit is available if its TCP socket is open.

Test

  GET /clammit/test/

This will return a simple file upload page, to test sending requests to Clammit. These pages are located in the testing/ sub-directory.

Web app

In the web/ directory is a simple Sinatra server to act as the application (for testing purposes).

Tests

Run make test

Limitations

  • Clammit does not implement HTTPS, as it is not intended to be a front-line server.
  • It does not attempt to recursively scan fields - e.g. attachments in an email chain
  • It does not try to be particularly clever with storing the body, which means that a DOS attack by hitting it simultaneously with a gazillion small files is quite possible.

License

MIT

clammit's People

Contributors

atzoum avatar caarlos0 avatar dmandalidis avatar hectorj avatar jblackman avatar lleirborras avatar lucaspiller avatar stevekerrison avatar vjt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

clammit's Issues

Broken pipe when uploading content

Hello again.

Today, as an ouptut I get

2021/05/27 13:09:41 Unable to scan file (output.dat): write tcp 12.3.4.5:53620->6.7.8.9:3310: write: broken pipe
2021/05/27 13:09:41 Interceptor has deemed that this request should not be forwarded

It seems the upload takes too long and connection gets closed?
Can this timeout get increased preferably via configuration?

clammit runtime error

Could i please share some insights what this output means?
I especially would need to know if with this kind of error the clammit stops working entirely (then it would be a good target for a liveness probe btw), or if just the connection to the clamd fails for one try? Does the app recover from this by itself?

2021/05/21 13:44:55 http: panic serving 127.0.0.1:60806: runtime error: invalid memory address or nil pointer dereference
goroutine 120 [running]:
net/http.(*conn).serve.func1(0xc000106c80)
	/usr/local/go/src/net/http/server.go:1824 +0x153
panic(0x727e00, 0x998100)
	/usr/local/go/src/runtime/panic.go:971 +0x499
github.com/dutchcoders/go-clamd.(*Clamd).Ping(0xc000158430, 0x6e8d01, 0xc000060cc0)
	/go/pkg/mod/github.com/dutchcoders/[email protected]/clamd.go:118 +0x8a
clammit/scanner.(*Clamav).Ping(0xc000023cc0, 0xc000060cc0, 0x1b)
	/app/scanner/clamav.go:72 +0x2f
main.infoHandler(0x7f2da0, 0xc00014c460, 0xc00032a200)
	/app/main.go:356 +0x142
net/http.HandlerFunc.ServeHTTP(0x79ec10, 0x7f2da0, 0xc00014c460, 0xc00032a200)
	/usr/local/go/src/net/http/server.go:2069 +0x44
net/http.(*ServeMux).ServeHTTP(0xc000023d00, 0x7f2da0, 0xc00014c460, 0xc00032a200)
	/usr/local/go/src/net/http/server.go:2448 +0x1ad
net/http.serverHandler.ServeHTTP(0xc00014c1c0, 0x7f2da0, 0xc00014c460, 0xc00032a200)
	/usr/local/go/src/net/http/server.go:2887 +0xa3
net/http.(*conn).serve(0xc000106c80, 0x7f3660, 0xc000348180)
	/usr/local/go/src/net/http/server.go:1952 +0x8cd
created by net/http.(*Server).Serve
	/usr/local/go/src/net/http/server.go:3013 +0x39b

Some more infos. I am running the clammit with 1 vCPU and a minium of 756MB RAM, because I want the clammit to keep a payload of 500MB in RAM and I leave 256MB for the app itself (which feels quite too much for a golang app).
Is this sufficient? Would increasing resources solve the mentioned issue?

traefik compatibility

hi,

I wanted to switch from nginx to traefik as proxy to forward to clammit.
therefor i was looking how to set the X-Clammit-Backend with the given traefik possibilities.

I did not find any solution suitable for my needs.

my solution now is to patch your forwarder.go:

func (f *Forwarder) getApplicationURL(req *http.Request) *url.URL {
		 // Return the applicationURL if it's set
		 if f.applicationURL != nil && f.applicationURL.String() != "" {
				 return f.applicationURL
		 }
 
		 // Otherwise check for the X-Clammit-Backend header
		 url, err := url.Parse(req.Header.Get(applicationUrlHeader))
		 if err != nil {
				 f.logger.Panicf("Error parsing application URL in %s: %s (%s)", applicationUrlHeader, err.Error(), req.Header.Get(applicationUrlHeader))
				 return nil
		 }
 
		 if len(url.String()) == 0 {
				 **f.logger.Print("No application URL available - header %s is blank", applicationUrlHeader)
				 // patch by Top21
				 f.logger.Print("Try to build from X-Forwarded-")
				 url,err = url.Parse(req.Header.Get("X-Forwarded-Proto") + "://" + req.Header.Get("X-Forwarded-Host"))**
		 }
 
		 return url
 }

maybe this is helpful for others too and you can implement it somehow in the main code.

cheers,

frank

Documentation on setup in nginx is very sparse

working on setting this up to work with our app that uses flask/uwsgi, the location config is particularly causing issues:
not sure what to configure for the following 2 lines

for starters we plan to use a location like so:

location /api/~ ^(/[^/]+) {
...
}
will this successfully monitor any post calls to /api/all-subdirectories

proxy_set_header X-Clammit-Backend unix:$my_app/.unicorn.sock;

This line seems critically important, but what should the setting point to?

proxy_pass http://unix:$clammit_app/.unicorn.sock;

also critically important. should this point to the location of the clammit unix.sock?
i.e /var/run/.clammit.sock ?

is there any additional documentation on how to configure clammit in nginx?
any help on this would be greatly appreciated

Gracefully handle clamd unavailability / disconnects

As reported in #24, when clamd is not available or abruptly closes the connection, clammit panics due to a missing handling of the case in go-clamd.

Handle the case and log a meaningful error message, possibly forking go-clamd.

Determine clammit health

Hello,

as I would like to run clammit in a kubernetes environment, I am wondering how to determine liveness and readiness.
I have seen in the main.go that there already is a handler implemented for graceful shutdown which is great IMO.
Once entered graceful shutdown, clammit could communicate being not ready for new connections anymore via a /readyz endpoint to let kubernetes not send traffic to the clammit pod anymore.
Not sure to if a dedicated liveness check and endpoint would be needed.

Check content body for all request types, not only for POST, PUT, PATCH

Hello!

Because of RFC 7230

The presence of a message body in a request is signaled by a
Content-Length or Transfer-Encoding header field. Request message
framing is independent of method semantics, even if the method does
not define any use for a message body.

I think clammit should perform virus checks for all method types, not only for POST, PUT, PATCH.

This could be easily implemented by removing POST, PUT, PATCH checks and just comparing Content-Length and/or actual body size to 0.

Misleading information in README

Hello!

I'm considering using this tool to perform virus scans in my project. Hope it fits perfectly!

While reading documentation in README file, noticed these lines of code:

4. The only request that will be tested will have methods POST/PUT/PATCH

This seems like a hole in the security. A malicious actor could just change the method to GET, for example, and, if the application doesn't care about the HTTP method used, it could receive a malicious file through GET request.

Found out that you already changed the conditions under which the request would be forwarded to a scanner in this issue: #21.

So maybe you just forgot to update the docs? If so, I could rephrase that and submit a PR, if you will.

Force scanning despite missing content-header when using s3api?

We recently tested uploading through clammit to a s3 bucket with "aws cli".

Choosing s3api as upload method, it seems that one could bypass virus scanning by just not providing the correct HTTP Header.
So, this one here works as expected and EICAR gets detected:

aws --endpoint https://192.168.1.180 s3api put-object --key e.zip.bin --bucket mybucket --body e.zip.bin --content-type=text/plain --no-verify-ssl

Log from clammit:

2022/12/20 14:05:20 Interceptor has deemed that this request should not be forwarded
2022/12/20 14:05:29 Received scan request
2022/12/20 14:05:29 Passing to interceptor
2022/12/20 14:05:29 New request PUT /mybucket/e.zip.bin len 235 from @ (192.168.1.180)
2022/12/20 14:05:29 Sending to clamav
2022/12/20 14:05:29   result of scan: Status: FOUND; Virus: true; Description: Eicar-Signature
2022/12/20 14:05:29 Interceptor has deemed that this request should not be forwarded

so far, so good.

Leaving "content-type" out as option, the following happens:

aws --endpoint https://192.168.1.180 s3api put-object --key e.zip.bin --bucket mybucket --body e.zip.bin --no-verify-ssl

Clammit skips scanning with "unable to parse media type error" and forwards the eicar file:

2022/12/20 14:10:09 Received scan request
2022/12/20 14:10:09 Passing to interceptor
2022/12/20 14:10:09 New request PUT /mybucket/e.zip.bin len 235 from @ (192.168.1.180)
2022/12/20 14:10:09 Unable to parse media type: mime: no media type
2022/12/20 14:10:09 Interceptor passed this request
2022/12/20 14:10:09 Forwarding to https://object.storage/
2022/12/20 14:10:09 Request forwarded, response 200 OK

Is it possible to force scanning, although Clammit can't check the media type?
Would #18 help here as well?

Thanks very much.

Test uploaded virus files cannot be intercepted

[root@localhost tmp]# curl -sf http://172.20.100.20:8438/clammit/test -d @mozi.m -v

  • About to connect() to 172.20.100.20 port 8438 (#0)
  • Trying 172.20.100.20...
  • Connected to 172.20.100.20 (172.20.100.20) port 8438 (#0)

POST /clammit/test HTTP/1.1
User-Agent: curl/7.29.0
Host: 172.20.100.20:8438
Accept: /
Content-Length: 46060
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

< HTTP/1.1 301 Moved Permanently
< Location: /clammit/test/
< Date: Mon, 22 Nov 2021 08:06:18 GMT
< Content-Length: 0
< Connection: close
<

  • Closing connection 0
    [root@localhost tmp]# curl -sf http://172.20.100.20:8438/clammit -d @mozi.m -v
  • About to connect() to 172.20.100.20 port 8438 (#0)
  • Trying 172.20.100.20...
  • Connected to 172.20.100.20 (172.20.100.20) port 8438 (#0)

POST /clammit HTTP/1.1
User-Agent: curl/7.29.0
Host: 172.20.100.20:8438
Accept: /
Content-Length: 46060
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Mon, 22 Nov 2021 08:06:50 GMT
< Content-Length: 310
< Connection: close
<

  • Closing connection 0
    {"clammit_version":"0.7.3","scan_server_url":"tcp://172.20.100.2:3310","ping_result":"Connected to server OK","scan_server_version":"ClamAV 0.103.3/26360/Sun Nov 21 17:19:26 2021","test_scan_virus":"Status: FOUND; Virus: true; Description: Win.Test.EICAR_HDB-1","test_scan_clean":"Status: CLEAN; Virus: false"}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.