Archived
Implement CSRF protection for REST interface (fixes #287)
This commit is contained in:
1 parent
028e9bc17a
commit
80c2b32b92
4 files changed
+120
-1
No files matched your search
+1
-1
@@ -18,7 +18,7 @@ func init() {
|
||||
bs, _ = ioutil.ReadAll(gr)
|
||||
Assets["angular.min.js"] = bs
|
||||
|
||||
bs, _ = hex.DecodeString("1f8b080000096e8800ffec3c6b73db3892dffd2b30bacc929ac89493dbddbb8ae36c659ccc9e372f579ccc87f3f8aa28119238a6488504eda83cfaefd70d80149e146d273399aa5555628968f40b8d467703e0784c8e8bd5ba4ce70b46c2e321797cf0e8afe45ff16531213f16e59cc479420ab6a0259916392bd349cd8ab28ac8f32c23bc57454a5ad1f28a26d11e60fb585152cc085ba415a98aba9c52e89850023fe7c5152d739a90c91ad09237271ff62bb6ce28c9d229cda11f5bc48c4ca1694211d5aca881789ac3734a5e9f1cbf7c7bf692ccd28c467b7be31f7eadb23467645216d740fe0961654d479cc934af69f37b95d515fe13bfc90f63e839cf8a499c91074fc82cce2a008af3799dc5a5fc8d407b410d5f2a9077ca82c3bdbdabb824d53a9f8258f99c1c353da26591d4190d83b62d1891f38be121ef5097d924063447240015713c2d5cc4b55964192dc3e0ac797accca0c30cc6af89d1639091f54d362051c3e5830b61a929b3d021fc4bd2ae9d58b9821f283c3f6e99cb277afe011caba7d8ac4e39209ceb988c009360aecc84a4e39c10a006e368746e32c9ddbcf97eb93172859a03dcd61a811c9f98503c9498e826aecc9f65559b0625a64c70b502c1848cba60243cb120ccfc65d519abfc4369b1b181d9ad9ac9774553492f2e7606b6730d4f9bc02cb9b15252593a2c82a9215c5253c618c963a45c63830f0c29fdfa4c91312bc4e2b4673400423085a98823df19125a7523a2220c8f3248121a96805806cbd025b0d18fdcce0971c2961ac9b9182fc4dfcf98ce6c9abc9aa52d0bfabd9bcc0717d8fa6f03a5da630895fa53f8eabe116775e2f27b4ecc4fe1ef0c5f9490e825ec5d9994241b490a68984b7c62cadcb8d5c36de1dff695cc63089b2f7f4530d40aa7240650414043df384eb680bd21f3f20114669681e91ff04ae8888563100fd55af50f827f746cff3fc651e4f329a28444413799182dd81eb5c6f11a37d76f2fdba987a90f2967be13c2d4ae6474864737f1d9fe1f31f851f57e70e3e26dbe72a9b5aff8fa7f9a92da77842b0d1ddf942f782f33a3d734f6c395fd581f978729fc9fc511714b13daf619183a56b1a73c7ff51137927c2d3b8aaae8b32e946aa4049c4abed934e6e3fbc56a72d2ef1fff3e1c3e919acd0250142bd6ca85177bbb8c16275564fa7942634099bb50d3fe98c84dff1a54c7daa0c559aa72c1c1eea4d61f01f396520cd255f0d8221baff380b83459ad0c080b6174afc6c3416b6cbe62dd9d876ec660261ab45cd92e23aef8674ade03ad31b5bb93fc5e09f6ccdfac52a29abcbdca70ef780f8b47e3389a7974959acc028801ed82098c5255d4f8ab84c64a0b5f10c4a9780ed123e03391608dcc64a2a6f3c5e8a0061d804610f4930aed6306597c06385765755e1b67312b3d8144e37d0439709088cc006f657581e8a7025747327913703a4f6db7e7f37f91556c6087456856ae0328c60d6bd8ca70b05799a5803e354000f88fe81688e027840730ccd3fbe3f392e96ab2207578198faaa475103c77b9e261796264ca9d4ef6e1e9568b4f74861949b17d7401d63628842afc3e148836018548608b3dfc6ce4332268f0e0e0e74c834918eaaf928a136f4d7454307083ac324c5a722eecdb0315ac4d5bbeb1ca2c1152dd99aabda018f9f268b39b45a37d613064bae1b0b12c541014705c10bb0ff26668b68197f0e0f46e4bfc90f42971ce224ff71cd68f5a160b094ef3b12030b0a95c792a1cda146b9a8d92ed210a2f5a1ad8179896f207d64d30509a94fb596520efa8ae000d44763e3f2107a7265ba895d9341243dbde781992ab9c96d0ef7cc7408622d56579a3bc5c7e6da81ab3c24f7daac473898f747907d41ba4e672964f8817b6981d021bfcc71b553bdbb46c1460d03058901ccb1ef908417f3192b562ba0ecc4ccd37726e670809ecf4186b76f7b37e0fceff9c145c48a8f40a03c86a10167fe5034c0b84c2a56868f8687ba1cb2fb1119600a088bed80fcf61bd93e3d49323a306511cd0fa19984832d97583438a5e514fc733ca772601e92c1f7c381535aa9124520c7881f6710fb7dfd014ff359f115463bc1a4abfc6283ad8d5890c2d87829cb8918f8c2a42d9a4a0cbc17d3aa4c973124602e4c0ef51963a81bc5571f4858277d123bd42b2a6cdc5f73fc07fdb06a43b89a32900a2060b570da094e2c4162ec0250783834f5ca57a3595640840664dc6e114b590eb7888f8f675aec8cdca29b0738c7e2253b446fe1efc98b0bdde810d0d44df33cc2a82ca39c2c2a1154e15ad45ad7ba22ace0f14f60845f846255d3df537a2812e2643129c32af4fdd0c4d8e175025e08e0e2b7ded8a1d993294af567d06b71795b7dcae4e4364a5b42b457f9b5653beb6f565db67feca533db19eed499df37a2ac5897f99d352619e34a9365a12edffe8f8ef15654fe8789603881ce75ca2fcacf74e73020333aa7dcd0948d060f9b12e28a9615e076f1f735d593a5b0f6feeca7bd639c61b94d90b0a51d5d5c946089390ddf5751b7590043c6a85a63407e1bb2f9569544603dd4f20d1451e28d329acfd902422ef2c823711b1f74082ab141acec3586b7f1d296d7610d325a91ad3d4394c1c017a1b4e30ce43d9d55900e19758b69a27fc869ffee0e22701bf4ad4b70738873610946b7d01830452f7d9512d622e5e29d869d2adaa59f3ecae9ad199aa44ca9b7bb35331e43a87649494cb0c288310af45db7cddaee622441dead9a64bbd9a4c53ea10e2b81d4fcdb850b8be89d780040c581055d2953df4a28af90bb5554c557b4b78aaaba84ff8a25155bd453b193aa3bc27677f5bb5626faa98eb3caad9d51a7828790d05acbb9f8ec400f4af3a0d6d599ce42c9f250b74990b9a214edb6d93826d76996919c827410044f682b2ba84b16bc8de0c6e629121b1d3cf3f471276186de4aa8bd93ad6f318889634af37cb5cad6c0fe3569f79633dc55cbd67b0e1afa28751ba83166ce22f65605bd708941f2236ab7f98d3abe5782486ca5c9908998dad7a1ce581955ab2c656130c23916af9405f0b3b2007e8e18c494e04189b9cf204a6daba2b20acf400e26e7bfcedebd8d2a7e20209d194a188ec8cd82c6092cfb4fc84d705ce4c013dbff008e3980091ec340caedb6f1af5591071badf8bdd7ed2a06b8f733f055e9b8157bfd80b645a4db9c674feab65b341eb549cc81cf971aa73e54c775769d62b1f69a4e5658c36827334cec9cefbaa8aec03dc186f646408b06d6476439308cb563eedbf3da446625363a6e18d20fe99216356bad3274388b6b88fd8aeb08e73882440a95e6ab4166c4772a3cf3aef3f88ce092ff6f2c2f72ebf14e26e5b6056537d32e58bbf60eadedcfdb1ae5a6a7d939e4c708c419801b016983b62e4b98eab2c783887e86999f843730344d278b152401ea7bf9195c97538d1ad819cd666297cac885b454c89677cb58d49e460037498eLine truncated
|
||||
bs, _ = hex.DecodeString("1f8b080000096e8800ffec3cfd73db36b2bffbaf40f5d223d5c894937777ef4d5ce72675d23b5fbe3c71d279f35cbf194a8424d614a910a06d8dabfffded0220852f52b49db4bd99f34c1b1b58ec17168bdd05c0f1981c17ab7599ce179c84c743f2f4e0c99fc93fe3cb62427e28ca3989f384147c414b322d725ea6938a17258bc88b2c236214232565b4bca249b407d83e314a8a19e18b94115654e594c2c08412f8735e5cd132a70999ac012d797bf2719ff1754649964e690ee3f822e6640a5d138aa8664505c4d31cda29797372fceaddd92b324b331aeded8dbffb856569cec9a42cae81fc33c2cb8a8e0493695ed1faef555631fc4ffe4dbe1bc3c879564ce28c3c7a466671c60028cee7551697ea6f04da0b2af88581bc531e1ceeed5dc52561eb7c0a62e57372548f8896455265340c9abe6044ce2f8687624055669318d01c91005424f0347011303a4be7e1ac8286b4c849f868c1f9eab42caed284964372bb47e0c7688c123a8bab8cb3e88695b37fd018dadec54b41e07ff68fcf3efcb8ffb1b8a43910da31f6b8282e535a8f35466e86369bbc2cb28c96617056b71ef332034135ded9b458812205c99a7754c1aaa4572f638e640e0e9bd639e5ef5f43134ec9b6157514975c2a58cc0470220411d891959c0a820c006e37875627a8d36d5fae4f5ea28c81d19a83452292f30b0f92931c0535d853fdabb2e0c5b4c88e1730ff60c70d9b1a0c2d4b581f2e6e4669fe0afb5c6ec08868e6b25ed255514b2ada61499c8145e673060b645694944c8a22632483d98416ce696952e45c00032fa2fd364d9e91e04dca38cd0111cc2068610a662f66969c2ae98884202f9204a6845106807cbd822515707ac3e12f3553724d6d461af2b7f1cd19cd93d79315d3d0bfaff8bcc079fd80a6f0265da6e06b5ea73f8cd9708b3baf96135a7662ff00f8e2fc240741afe2ec4ca3207b48dd45c23b6356d6e547ae3aef8fff342e635844d907fab902205d39a032020a8291792274b405e98f1f9048a3b4348fc87f048f4964af9c80feaad728fc5d38cd1779fe2a8f27194d3422b28bbc4cc1eec0c3afb788d13e3bf97e534c5b908a9e07e13c2d4ade8e90a8eefe3a3ec3f61fe476a3af1d6c26db769d4d63fca7d3fcd49553b610ecf40fbe30bde0bc4acffc0b5bad577d623e9d3c64317f3205456c2f2ad88b61879dc6c2f17f3244de89f03466ecba28936ea41a9442bcdab67472fbf18dbe6c3112f9c7c78fa7671048940408f5b2a15addcde6069bd559359d529ad024acf736fc496724fc466c657aab3655699ef27078687685c17fe49483349762370886e8fee32c0c16b04d0716b4bb51e2cfc66061bb6dde918dedc06e2610962d2a9e14d77937a46f073799deb8cafd3106ffe46ab65dac92f2aaccdbd4e19f9036addf4ee2e96552162b300aa0073608667149d793222e13150f6e5a26a54bc0660b9f811c0b046e62259d37112f458030ac63c5c72418b3352cd925f0c8d0ee18d382c424e6b12d9c69a0873e139018810d1cafb13c94e14ae8e74e21af27481fb7fdfdfde417d81923d0190bf5c06518c1aa7b154f171af2347126c6ab001110fd0dd11c05d04073cc203e7d38392e96ab2207578198faaa475383c07b9e26178e266ca9f4dffd3c6ad168ef99c228372fae813ac6c410855e87c39101c131a80c1166bf899d87644c9e1c1c1c989069a21c55fda385da30de140d1d20e80c73a93615096f869dd12266efaf73880657b4e46ba16a0f3cfed4c9d6a1d3bb715a386cb97e2c481427051c15042fc0fedb982fa2657c131e8cc87f93efa42e05c449fec39a53f6b1e0b095ef7b1203070a95c793a1cba141b9a8f82ed210a2f5a16d80b512df4096cba70b12d236d53a4a39e82b8207d09c8d8dcf4398c995ed26762d0699f4f45e0776aae427b739dcb3d32188b578c50c778acdf6de81bb7c3133573dc2c1ba3f82ecabca21194e7308c3fc5b0b840ef9658ebb9deedd0d0a2e6a9828480c608d7d83245a319ff162b502ca5ecca2cac0e51a0ed0f379c888feede81a5cfc7b7e7011f1e21310288f616ac0993f961d302f13c6cbf0c9f0d094430d3f22034c0161b31d905f7f25dbd69324a3035b16d9fd18ba4938d872894583535a4ec13fc773aa26e631197c3b1c78a5552ad104f2ccf87106b1dfd79ff0349f155f61b6134cbaca2f36d9c68c0529cc4d2b65b51083b630698b86c9896fc5b42ad3650c09980f93477dd61c9a46f1d52712f6c936893dea958540e1af05fe837e588d295c4d39480510b05b78ed0417962431f601683c1cda7a15bbd12c2b204203327eb788a52c8f5bc4e6e399113b23b7e8e601ceb379a901d13bf8f7e4e58569740868eba66e8f302acba8208b4a0455f836b5c6b5ae082f44fc1358e117a1587c6d1fa93c140971b1d8946117fa766863ecf03a81280408f11b6fecd1ecc914a5fa57d06b7179577daae4e42e4a5b42b4c7dab5e53aeb3facba5cffd84b67ae33dca9b376df88b2625de637d698624c284d9585ba7cfbdf3ae65b53f9ef2682e5043af7a976517ea23ba7019931391586a61d34b4b0a920ae68c900b78fbfafa99e2c85bdf7a776da3be619b6db04093bda31c545099698d3887315fd980530649cea3506e4b7269b6f554924d64323df401115de28a3f99c2f20e4224f5a246ee2830e41153688955b8d411d8cedb40615ada8de9e21ca60d016a134f30ce45b06eb201d329a165347ff90d3fed51f44e069ed3b9fe0f614e7d212ac61a135619a5efa2a25ac64ca25060d3b55b44b3f7d94d35b333449b9566ff76b663c8650ed929298608511631418bb6eba8dd3c54881bc5fd5c9767d968c6342135601e9f9b70f1716d13bf100808e030bba4aa6be95505121f7ab88c557b4b78a5855c2ff8a259527e95379926a3ac2e674f59b4626fab98a33e6d7cea853c14348689ded5cfeec400f4a6b416daa339d858ae5a169932033a314edb63e3826d76996919c827410044f68232ba84b15bcade0c6e52992071d22f36ce34ec10c5b2ba1ee49b679c420178e2dcd8bd52a5b03fbd7a4395bcef0542d5bef796898b3d46da0d69c798bd85b15f4c22527a91d5173cc6fd5f15b2588e4519a0a9988ad7d13ea8c97115b65290f8311aeb178a56d8037da067813718829c18312fb9c4196da5605730acf400e16e73fcfdebf8b98b81090ce2c250c47e476212e88b067e436382e72e089ef7f04c71cc0028f6122d571dbf81756e4c1c6287eef75bb8a019efd0cdaaa74c28a5bfd80714464da5ccb99d45d8f685ad4a630076dbed4baf5a13baeb3eb148bb5d774b2c21a46b3986161e7e2d4457705fe0536740f021a34b03f22cb8165ac1d6bdf5dd7363227b13171c3947e4c97b4a8786395a1c7595c43ec575c47b8c61124d2a8d4bf5a6446e2a4a265dd755e9f915c8aff5bdb8b3a7abc9749f96d413bcd740bd6beb343e7f8f3ae46b9e969761ef93102Line truncated
|
||||
gr, _ = gzip.NewReader(bytes.NewBuffer(bs))
|
||||
bs, _ = ioutil.ReadAll(gr)
|
||||
Assets["app.js"] = bs
|
||||
|
||||
@@ -105,6 +105,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
router.Post("/rest/discovery/hint", restPostDiscoveryHint)
|
||||
|
||||
mr := martini.New()
|
||||
mr.Use(csrfMiddleware)
|
||||
if len(cfg.User) > 0 && len(cfg.Password) > 0 {
|
||||
mr.Use(basic(cfg.User, cfg.Password))
|
||||
}
|
||||
@@ -114,6 +115,8 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
mr.Action(router.Handle)
|
||||
mr.Map(m)
|
||||
|
||||
loadCsrfTokens()
|
||||
|
||||
go http.Serve(listener, mr)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/calmh/syncthing/osutil"
|
||||
)
|
||||
|
||||
var csrfTokens []string
|
||||
var csrfMut sync.Mutex
|
||||
|
||||
// Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
|
||||
// the request with 403. For / and /index.html, set a new CSRF cookie if none
|
||||
// is currently set.
|
||||
func csrfMiddleware(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/rest/") {
|
||||
token := r.Header.Get("X-CSRF-Token")
|
||||
if !validCsrfToken(token) {
|
||||
http.Error(w, "CSRF Error", 403)
|
||||
}
|
||||
} else if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
cookie, err := r.Cookie("CSRF-Token")
|
||||
if err != nil || !validCsrfToken(cookie.Value) {
|
||||
cookie = &http.Cookie{
|
||||
Name: "CSRF-Token",
|
||||
Value: newCsrfToken(),
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validCsrfToken(token string) bool {
|
||||
csrfMut.Lock()
|
||||
defer csrfMut.Unlock()
|
||||
for _, t := range csrfTokens {
|
||||
if t == token {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newCsrfToken() string {
|
||||
bs := make([]byte, 30)
|
||||
_, err := rand.Reader.Read(bs)
|
||||
if err != nil {
|
||||
l.Fatalln(err)
|
||||
}
|
||||
|
||||
token := base64.StdEncoding.EncodeToString(bs)
|
||||
|
||||
csrfMut.Lock()
|
||||
csrfTokens = append(csrfTokens, token)
|
||||
if len(csrfTokens) > 10 {
|
||||
csrfTokens = csrfTokens[len(csrfTokens)-10:]
|
||||
}
|
||||
defer csrfMut.Unlock()
|
||||
|
||||
saveCsrfTokens()
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
func saveCsrfTokens() {
|
||||
name := filepath.Join(confDir, "csrftokens.txt")
|
||||
tmp := fmt.Sprintf("%s.tmp.%d", name, time.Now().UnixNano())
|
||||
|
||||
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmp)
|
||||
|
||||
for _, t := range csrfTokens {
|
||||
_, err := fmt.Fprintln(f, t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
osutil.Rename(tmp, name)
|
||||
}
|
||||
|
||||
func loadCsrfTokens() {
|
||||
name := filepath.Join(confDir, "csrftokens.txt")
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
csrfTokens = append(csrfTokens, s.Text())
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,11 @@
|
||||
var syncthing = angular.module('syncthing', []);
|
||||
var urlbase = 'rest';
|
||||
|
||||
syncthing.config(function ($httpProvider) {
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
|
||||
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token';
|
||||
});
|
||||
|
||||
syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
var prevDate = 0;
|
||||
var getOK = true;
|
||||
|
||||
Reference in new issue
Block a user