Skip to content

Commit 3e34968

Browse files
committed
Add 'security' hint group (enabled by default): weak hashes, read on untrusted input
data/hlint.yaml: new group 'security', enabled: true. Rules: read x -> Text.Read.readMaybe x side: not (isLitString x) CWE-502 reads x -> Text.Read.readMaybe x side: not (isLitString x) CWE-502 Crypto.Hash.MD5.hash -> Crypto.Hash.SHA256.hash CWE-327 Crypto.Hash.MD5.hashlazy -> Crypto.Hash.SHA256.hashlazy CWE-327 Crypto.Hash.SHA1.hash -> Crypto.Hash.SHA256.hash CWE-327 Crypto.Hash.SHA1.hashlazy -> Crypto.Hash.SHA256.hashlazy CWE-327 Data.Digest.Pure.MD5.md5 -> Crypto.Hash.SHA256.hashlazy CWE-327 Data.Digest.Pure.SHA.sha1 -> Data.Digest.Pure.SHA.sha256 CWE-327 Disable per-rule via `-i "<name>"` or suppress the whole group with `- group: {name: security, enabled: false}` in .hlint.yaml. tests/security.test: four cases. 1. security-hash.hs : all six hash rules fire. 2. security-read-nonliteral.hs: `read x` fires for variable `x`. 3. security-read-literal.hs : `read "42"` and `read "3.14"` do not fire. 4. security-ignored.hs : per-rule -i suppression yields no hints. Self-test: 974 tests, 3 failures (all pre-existing on master: issue #1674 intercalate/OverloadedStrings and two record-pattern type-application cases from PR #1680). Running hlint with the new group on hlint's own source produces one finding: src/Test/InputOutput.hs:57 `read code` (parsing an EXIT line from a .test file). No HSEC advisory on github.com/haskell/security-advisories maps to CWE-327 or CWE-502 for these function classes.
1 parent 740ac67 commit 3e34968

2 files changed

Lines changed: 198 additions & 0 deletions

File tree

data/hlint.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,40 @@
14771477
- warn: {lhs: foldr' f c (reverse x), rhs: foldl' (flip f) c x, name: Use left fold instead of right fold}
14781478
- warn: {lhs: foldl' f c (reverse x), rhs: foldr (flip f) c x, note: IncreasesLaziness, name: Use right fold instead of left fold}
14791479

1480+
- group:
1481+
# Security hints, enabled by default. Disable per-rule with
1482+
# `- ignore: {name: "..."}` in .hlint.yaml, or suppress the whole
1483+
# group with `- group: {name: security, enabled: false}`.
1484+
#
1485+
# Rule categories:
1486+
# * `read` -> `readMaybe` on non-literal arguments (CWE-502)
1487+
# * broken / weak hash primitives: MD5, SHA-1 (CWE-327)
1488+
name: security
1489+
enabled: true
1490+
rules:
1491+
1492+
# `read` throws a runtime exception on malformed input; it is the
1493+
# Haskell analogue of Python's `pickle.load` / `ast.literal_eval` on
1494+
# untrusted data. Replace with `readMaybe` from `Text.Read` and
1495+
# handle `Nothing` explicitly. Literal arguments (e.g. `read "42"`)
1496+
# are not flagged because the failure is a programmer bug caught on
1497+
# first run, not a runtime attack surface.
1498+
- warn: {lhs: read x, rhs: Text.Read.readMaybe x, side: not (isLitString x), name: "Use readMaybe on untrusted input (CWE-502)"}
1499+
- warn: {lhs: reads x, rhs: Text.Read.readMaybe x, side: not (isLitString x), name: "Use readMaybe on untrusted input (CWE-502)"}
1500+
1501+
# Broken or weak cryptographic hashes. MD5 and SHA-1 are
1502+
# cryptographically broken (MD5: chosen-prefix collisions since 2008;
1503+
# SHA-1: SHAttered, 2017) and must not be used for any security
1504+
# purpose (authentication, integrity, digital signatures). Use
1505+
# SHA-256 or stronger from `Crypto.Hash` (cryptonite/crypton); for
1506+
# password hashing use `Crypto.KDF.BCrypt` or `Crypto.KDF.Argon2`.
1507+
- warn: {lhs: Crypto.Hash.MD5.hash x, rhs: Crypto.Hash.SHA256.hash x, name: "Avoid broken hash MD5 (CWE-327)"}
1508+
- warn: {lhs: Crypto.Hash.MD5.hashlazy x, rhs: Crypto.Hash.SHA256.hashlazy x, name: "Avoid broken hash MD5 (CWE-327)"}
1509+
- warn: {lhs: Crypto.Hash.SHA1.hash x, rhs: Crypto.Hash.SHA256.hash x, name: "Avoid broken hash SHA-1 (CWE-327)"}
1510+
- warn: {lhs: Crypto.Hash.SHA1.hashlazy x, rhs: Crypto.Hash.SHA256.hashlazy x, name: "Avoid broken hash SHA-1 (CWE-327)"}
1511+
- warn: {lhs: Data.Digest.Pure.MD5.md5 x, rhs: Crypto.Hash.SHA256.hashlazy x, name: "Avoid broken hash MD5 (CWE-327)"}
1512+
- warn: {lhs: Data.Digest.Pure.SHA.sha1 x, rhs: Data.Digest.Pure.SHA.sha256 x, name: "Avoid broken hash SHA-1 (CWE-327)"}
1513+
14801514
- group:
14811515
# used for tests, enabled when testing this file
14821516
name: testing

tests/security.test

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---------------------------------------------------------------------
2+
RUN tests/security-hash.hs --hint=data/hlint.yaml
3+
FILE tests/security-hash.hs
4+
module Sample where
5+
6+
import qualified Crypto.Hash.MD5 as MD5
7+
import qualified Crypto.Hash.SHA1 as SHA1
8+
import qualified Data.Digest.Pure.MD5 as PMD5
9+
import qualified Data.Digest.Pure.SHA as PSHA
10+
import qualified Data.ByteString as BS
11+
12+
input :: BS.ByteString
13+
input = BS.empty
14+
15+
viaMD5, viaMD5Lazy, viaSHA1, viaSHA1Lazy :: BS.ByteString
16+
viaMD5 = MD5.hash input
17+
viaMD5Lazy = MD5.hashlazy undefined
18+
viaSHA1 = SHA1.hash input
19+
viaSHA1Lazy = SHA1.hashlazy undefined
20+
21+
viaPureMD5 = PMD5.md5 undefined
22+
viaPureSHA1 = PSHA.sha1 undefined
23+
OUTPUT
24+
tests/security-hash.hs:13:10-23: Warning: Avoid broken hash MD5 (CWE-327)
25+
Found:
26+
MD5.hash input
27+
Perhaps:
28+
Crypto.Hash.SHA256.hash input
29+
30+
tests/security-hash.hs:13:10-23: Warning: Avoid broken hash MD5 (CWE-327)
31+
Found:
32+
MD5.hash input
33+
Perhaps:
34+
Crypto.Hash.SHA256.hash input
35+
36+
tests/security-hash.hs:14:14-35: Warning: Avoid broken hash MD5 (CWE-327)
37+
Found:
38+
MD5.hashlazy undefined
39+
Perhaps:
40+
Crypto.Hash.SHA256.hashlazy undefined
41+
42+
tests/security-hash.hs:14:14-35: Warning: Avoid broken hash MD5 (CWE-327)
43+
Found:
44+
MD5.hashlazy undefined
45+
Perhaps:
46+
Crypto.Hash.SHA256.hashlazy undefined
47+
48+
tests/security-hash.hs:15:11-25: Warning: Avoid broken hash SHA-1 (CWE-327)
49+
Found:
50+
SHA1.hash input
51+
Perhaps:
52+
Crypto.Hash.SHA256.hash input
53+
54+
tests/security-hash.hs:15:11-25: Warning: Avoid broken hash SHA-1 (CWE-327)
55+
Found:
56+
SHA1.hash input
57+
Perhaps:
58+
Crypto.Hash.SHA256.hash input
59+
60+
tests/security-hash.hs:16:15-37: Warning: Avoid broken hash SHA-1 (CWE-327)
61+
Found:
62+
SHA1.hashlazy undefined
63+
Perhaps:
64+
Crypto.Hash.SHA256.hashlazy undefined
65+
66+
tests/security-hash.hs:16:15-37: Warning: Avoid broken hash SHA-1 (CWE-327)
67+
Found:
68+
SHA1.hashlazy undefined
69+
Perhaps:
70+
Crypto.Hash.SHA256.hashlazy undefined
71+
72+
tests/security-hash.hs:18:14-31: Warning: Avoid broken hash MD5 (CWE-327)
73+
Found:
74+
PMD5.md5 undefined
75+
Perhaps:
76+
Crypto.Hash.SHA256.hashlazy undefined
77+
78+
tests/security-hash.hs:18:14-31: Warning: Avoid broken hash MD5 (CWE-327)
79+
Found:
80+
PMD5.md5 undefined
81+
Perhaps:
82+
Crypto.Hash.SHA256.hashlazy undefined
83+
84+
tests/security-hash.hs:19:15-33: Warning: Avoid broken hash SHA-1 (CWE-327)
85+
Found:
86+
PSHA.sha1 undefined
87+
Perhaps:
88+
PSHA.sha256 undefined
89+
90+
tests/security-hash.hs:19:15-33: Warning: Avoid broken hash SHA-1 (CWE-327)
91+
Found:
92+
PSHA.sha1 undefined
93+
Perhaps:
94+
PSHA.sha256 undefined
95+
96+
12 hints
97+
98+
---------------------------------------------------------------------
99+
RUN tests/security-read-nonliteral.hs --hint=data/hlint.yaml
100+
FILE tests/security-read-nonliteral.hs
101+
module Sample where
102+
103+
parseStr :: String -> Int
104+
parseStr s = 1 + read s
105+
106+
parseOther :: String -> Double
107+
parseOther raw = 0.5 * read raw
108+
OUTPUT
109+
tests/security-read-nonliteral.hs:4:18-23: Warning: Use readMaybe on untrusted input (CWE-502)
110+
Found:
111+
read s
112+
Perhaps:
113+
Text.Read.readMaybe s
114+
115+
tests/security-read-nonliteral.hs:4:18-23: Warning: Use readMaybe on untrusted input (CWE-502)
116+
Found:
117+
read s
118+
Perhaps:
119+
Text.Read.readMaybe s
120+
121+
tests/security-read-nonliteral.hs:7:24-31: Warning: Use readMaybe on untrusted input (CWE-502)
122+
Found:
123+
read raw
124+
Perhaps:
125+
Text.Read.readMaybe raw
126+
127+
tests/security-read-nonliteral.hs:7:24-31: Warning: Use readMaybe on untrusted input (CWE-502)
128+
Found:
129+
read raw
130+
Perhaps:
131+
Text.Read.readMaybe raw
132+
133+
4 hints
134+
135+
136+
---------------------------------------------------------------------
137+
RUN tests/security-read-literal.hs --hint=data/hlint.yaml
138+
FILE tests/security-read-literal.hs
139+
module Sample where
140+
141+
answer :: Int
142+
answer = read "42"
143+
144+
piApprox :: Double
145+
piApprox = read "3.14"
146+
OUTPUT
147+
No hints
148+
149+
150+
---------------------------------------------------------------------
151+
RUN tests/security-ignored.hs --hint=data/hlint.yaml -i "Avoid broken hash MD5 (CWE-327)" -i "Use readMaybe on untrusted input (CWE-502)"
152+
FILE tests/security-ignored.hs
153+
module Sample where
154+
155+
import qualified Crypto.Hash.MD5 as MD5
156+
import qualified Data.ByteString as BS
157+
158+
digest :: BS.ByteString
159+
digest = MD5.hash BS.empty
160+
161+
parseStr :: String -> Int
162+
parseStr s = 1 + read s
163+
OUTPUT
164+
No hints

0 commit comments

Comments
 (0)