{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TupleSections #-}

-- |
-- Module    : Aura.Pkgbuild.Security
-- Copyright : (c) Colin Woodbury, 2012 - 2020
-- License   : GPL3
-- Maintainer: Colin Woodbury <colin@fosskers.ca>
--
-- Analyse PKGBUILDs for potentially malicious bash code.

module Aura.Pkgbuild.Security
  ( BannedTerm(..), BanCategory(..)
  , parsedPB, bannedTerms
  , reportExploit
  ) where

import           Aura.Languages
import           Aura.Types (Language, Pkgbuild(..))
import           Aura.Utils (hush)
import           Data.Text.Prettyprint.Doc (Doc)
import           Data.Text.Prettyprint.Doc.Render.Terminal (AnsiStyle)
import           Language.Bash.Parse (parse)
import           Language.Bash.Syntax
import           Language.Bash.Word
import           RIO hiding (Word)
import           RIO.Lens (each, _Just)
import qualified RIO.Map as M
import qualified RIO.Text as T

---

-- | A bash term which should never appear in a PKGBUILD. If one does, it's
-- either a sign of maintainer negligence or malicious behaviour.
data BannedTerm = BannedTerm !Text !BanCategory
  deriving (BannedTerm -> BannedTerm -> Bool
(BannedTerm -> BannedTerm -> Bool)
-> (BannedTerm -> BannedTerm -> Bool) -> Eq BannedTerm
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: BannedTerm -> BannedTerm -> Bool
$c/= :: BannedTerm -> BannedTerm -> Bool
== :: BannedTerm -> BannedTerm -> Bool
$c== :: BannedTerm -> BannedTerm -> Bool
Eq, Eq BannedTerm
Eq BannedTerm
-> (BannedTerm -> BannedTerm -> Ordering)
-> (BannedTerm -> BannedTerm -> Bool)
-> (BannedTerm -> BannedTerm -> Bool)
-> (BannedTerm -> BannedTerm -> Bool)
-> (BannedTerm -> BannedTerm -> Bool)
-> (BannedTerm -> BannedTerm -> BannedTerm)
-> (BannedTerm -> BannedTerm -> BannedTerm)
-> Ord BannedTerm
BannedTerm -> BannedTerm -> Bool
BannedTerm -> BannedTerm -> Ordering
BannedTerm -> BannedTerm -> BannedTerm
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: BannedTerm -> BannedTerm -> BannedTerm
$cmin :: BannedTerm -> BannedTerm -> BannedTerm
max :: BannedTerm -> BannedTerm -> BannedTerm
$cmax :: BannedTerm -> BannedTerm -> BannedTerm
>= :: BannedTerm -> BannedTerm -> Bool
$c>= :: BannedTerm -> BannedTerm -> Bool
> :: BannedTerm -> BannedTerm -> Bool
$c> :: BannedTerm -> BannedTerm -> Bool
<= :: BannedTerm -> BannedTerm -> Bool
$c<= :: BannedTerm -> BannedTerm -> Bool
< :: BannedTerm -> BannedTerm -> Bool
$c< :: BannedTerm -> BannedTerm -> Bool
compare :: BannedTerm -> BannedTerm -> Ordering
$ccompare :: BannedTerm -> BannedTerm -> Ordering
$cp1Ord :: Eq BannedTerm
Ord, Int -> BannedTerm -> ShowS
[BannedTerm] -> ShowS
BannedTerm -> String
(Int -> BannedTerm -> ShowS)
-> (BannedTerm -> String)
-> ([BannedTerm] -> ShowS)
-> Show BannedTerm
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BannedTerm] -> ShowS
$cshowList :: [BannedTerm] -> ShowS
show :: BannedTerm -> String
$cshow :: BannedTerm -> String
showsPrec :: Int -> BannedTerm -> ShowS
$cshowsPrec :: Int -> BannedTerm -> ShowS
Show, (forall x. BannedTerm -> Rep BannedTerm x)
-> (forall x. Rep BannedTerm x -> BannedTerm) -> Generic BannedTerm
forall x. Rep BannedTerm x -> BannedTerm
forall x. BannedTerm -> Rep BannedTerm x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep BannedTerm x -> BannedTerm
$cfrom :: forall x. BannedTerm -> Rep BannedTerm x
Generic)

banCatL :: Lens' BannedTerm BanCategory
banCatL :: (BanCategory -> f BanCategory) -> BannedTerm -> f BannedTerm
banCatL BanCategory -> f BanCategory
f (BannedTerm Text
t BanCategory
bc) = Text -> BanCategory -> BannedTerm
BannedTerm Text
t (BanCategory -> BannedTerm) -> f BanCategory -> f BannedTerm
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> BanCategory -> f BanCategory
f BanCategory
bc

-- | The reason why the bash term is black-listed.
data BanCategory = Downloading
                 | ScriptRunning
                 | Permissions
                 | InlinedBash
                 | StrangeBashism
                 | CleverRedirect
                 deriving (BanCategory -> BanCategory -> Bool
(BanCategory -> BanCategory -> Bool)
-> (BanCategory -> BanCategory -> Bool) -> Eq BanCategory
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: BanCategory -> BanCategory -> Bool
$c/= :: BanCategory -> BanCategory -> Bool
== :: BanCategory -> BanCategory -> Bool
$c== :: BanCategory -> BanCategory -> Bool
Eq, Eq BanCategory
Eq BanCategory
-> (BanCategory -> BanCategory -> Ordering)
-> (BanCategory -> BanCategory -> Bool)
-> (BanCategory -> BanCategory -> Bool)
-> (BanCategory -> BanCategory -> Bool)
-> (BanCategory -> BanCategory -> Bool)
-> (BanCategory -> BanCategory -> BanCategory)
-> (BanCategory -> BanCategory -> BanCategory)
-> Ord BanCategory
BanCategory -> BanCategory -> Bool
BanCategory -> BanCategory -> Ordering
BanCategory -> BanCategory -> BanCategory
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: BanCategory -> BanCategory -> BanCategory
$cmin :: BanCategory -> BanCategory -> BanCategory
max :: BanCategory -> BanCategory -> BanCategory
$cmax :: BanCategory -> BanCategory -> BanCategory
>= :: BanCategory -> BanCategory -> Bool
$c>= :: BanCategory -> BanCategory -> Bool
> :: BanCategory -> BanCategory -> Bool
$c> :: BanCategory -> BanCategory -> Bool
<= :: BanCategory -> BanCategory -> Bool
$c<= :: BanCategory -> BanCategory -> Bool
< :: BanCategory -> BanCategory -> Bool
$c< :: BanCategory -> BanCategory -> Bool
compare :: BanCategory -> BanCategory -> Ordering
$ccompare :: BanCategory -> BanCategory -> Ordering
$cp1Ord :: Eq BanCategory
Ord, Int -> BanCategory -> ShowS
[BanCategory] -> ShowS
BanCategory -> String
(Int -> BanCategory -> ShowS)
-> (BanCategory -> String)
-> ([BanCategory] -> ShowS)
-> Show BanCategory
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BanCategory] -> ShowS
$cshowList :: [BanCategory] -> ShowS
show :: BanCategory -> String
$cshow :: BanCategory -> String
showsPrec :: Int -> BanCategory -> ShowS
$cshowsPrec :: Int -> BanCategory -> ShowS
Show)

blacklist :: Map Text BannedTerm
blacklist :: Map Text BannedTerm
blacklist = [(Text, BannedTerm)] -> Map Text BannedTerm
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList ([(Text, BannedTerm)] -> Map Text BannedTerm)
-> [(Text, BannedTerm)] -> Map Text BannedTerm
forall a b. (a -> b) -> a -> b
$ [(Text, BannedTerm)]
downloading [(Text, BannedTerm)]
-> [(Text, BannedTerm)] -> [(Text, BannedTerm)]
forall a. Semigroup a => a -> a -> a
<> [(Text, BannedTerm)]
running [(Text, BannedTerm)]
-> [(Text, BannedTerm)] -> [(Text, BannedTerm)]
forall a. Semigroup a => a -> a -> a
<> [(Text, BannedTerm)]
permissions
  where
    downloading :: [(Text, BannedTerm)]
downloading = (Text -> (Text, BannedTerm)) -> [Text] -> [(Text, BannedTerm)]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
t -> (Text
t, Text -> BanCategory -> BannedTerm
BannedTerm Text
t BanCategory
Downloading)) [Text
"curl", Text
"wget", Text
"rsync", Text
"scp"]
    running :: [(Text, BannedTerm)]
running     = (Text -> (Text, BannedTerm)) -> [Text] -> [(Text, BannedTerm)]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
t -> (Text
t, Text -> BanCategory -> BannedTerm
BannedTerm Text
t BanCategory
ScriptRunning)) [Text
"sh", Text
"bash", Text
"eval", Text
"zsh", Text
"fish"]
    permissions :: [(Text, BannedTerm)]
permissions = (Text -> (Text, BannedTerm)) -> [Text] -> [(Text, BannedTerm)]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
t -> (Text
t, Text -> BanCategory -> BannedTerm
BannedTerm Text
t BanCategory
Permissions)) [Text
"sudo", Text
"ssh"]

-- TODO wasteful conversion!
-- | Attempt to parse a PKGBUILD. Should succeed for all reasonable PKGBUILDs.
parsedPB :: Pkgbuild -> Maybe List
parsedPB :: Pkgbuild -> Maybe List
parsedPB (Pkgbuild ByteString
pb) = Either ParseError List -> Maybe List
forall a b. Either a b -> Maybe b
hush (Either ParseError List -> Maybe List)
-> (Text -> Either ParseError List) -> Text -> Maybe List
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String -> Either ParseError List
parse String
"PKGBUILD" (String -> Either ParseError List)
-> (Text -> String) -> Text -> Either ParseError List
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> Maybe List) -> Text -> Maybe List
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
decodeUtf8Lenient ByteString
pb

-- | Discover any banned terms lurking in a parsed PKGBUILD, paired with
-- the surrounding context lines.
bannedTerms :: List -> [(ShellCommand, BannedTerm)]
bannedTerms :: List -> [(ShellCommand, BannedTerm)]
bannedTerms = List -> [ShellCommand]
simpleCommands (List -> [ShellCommand])
-> (ShellCommand -> [(ShellCommand, BannedTerm)])
-> List
-> [(ShellCommand, BannedTerm)]
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> ShellCommand -> [(ShellCommand, BannedTerm)]
bannedCommand

banned :: Word -> Maybe BannedTerm
banned :: Word -> Maybe BannedTerm
banned Word
w = Text -> Map Text BannedTerm -> Maybe BannedTerm
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Word -> String
unquote Word
w) Map Text BannedTerm
blacklist

-- | Extract all `SimpleCommand`s from a parsed bash AST.
simpleCommands :: List -> [ShellCommand]
simpleCommands :: List -> [ShellCommand]
simpleCommands (List [Statement]
ss) = [Statement]
ss [Statement] -> (Statement -> [ShellCommand]) -> [ShellCommand]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Statement -> [ShellCommand]
statements [ShellCommand]
-> (ShellCommand -> [ShellCommand]) -> [ShellCommand]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ShellCommand -> [ShellCommand]
p
  where
    p :: ShellCommand -> [ShellCommand]
    p :: ShellCommand -> [ShellCommand]
p sc :: ShellCommand
sc@(SimpleCommand [Assign]
_ [Word]
_) = [ShellCommand
sc]
    p ShellCommand
sc                     = ShellCommand -> [List]
lists ShellCommand
sc [List] -> (List -> [ShellCommand]) -> [ShellCommand]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= List -> [ShellCommand]
simpleCommands

    statements :: Statement -> [ShellCommand]
    statements :: Statement -> [ShellCommand]
statements (Statement AndOr
ao ListTerm
_) = AndOr -> [ShellCommand]
andor AndOr
ao

    andor :: AndOr -> [ShellCommand]
    andor :: AndOr -> [ShellCommand]
andor (Last Pipeline
pl)   = Pipeline -> [ShellCommand]
pipeline Pipeline
pl
    andor (And Pipeline
pl AndOr
ao) = Pipeline -> [ShellCommand]
pipeline Pipeline
pl [ShellCommand] -> [ShellCommand] -> [ShellCommand]
forall a. Semigroup a => a -> a -> a
<> AndOr -> [ShellCommand]
andor AndOr
ao
    andor (Or Pipeline
pl AndOr
ao)  = Pipeline -> [ShellCommand]
pipeline Pipeline
pl [ShellCommand] -> [ShellCommand] -> [ShellCommand]
forall a. Semigroup a => a -> a -> a
<> AndOr -> [ShellCommand]
andor AndOr
ao

    pipeline :: Pipeline -> [ShellCommand]
    pipeline :: Pipeline -> [ShellCommand]
pipeline (Pipeline Bool
_ Bool
_ Bool
_ [Command]
cs) = (Command -> ShellCommand) -> [Command] -> [ShellCommand]
forall a b. (a -> b) -> [a] -> [b]
map Command -> ShellCommand
command [Command]
cs

    command :: Command -> ShellCommand
    command :: Command -> ShellCommand
command (Command ShellCommand
sc [Redir]
_) = ShellCommand
sc

    lists :: ShellCommand -> [List]
    lists :: ShellCommand -> [List]
lists (SimpleCommand [Assign]
_ [Word]
_) = []
    lists (AssignBuiltin Word
_ [Either Assign Word]
_) = []
    lists (FunctionDef String
_ List
l)   = [List
l]
    lists (Coproc String
_ Command
c)        = ShellCommand -> [List]
lists (ShellCommand -> [List]) -> ShellCommand -> [List]
forall a b. (a -> b) -> a -> b
$ Command -> ShellCommand
command Command
c
    lists (Subshell List
l)        = [List
l]
    lists (Group List
l)           = [List
l]
    lists (Arith String
_)           = []
    lists (Cond CondExpr Word
_)            = []
    lists (For String
_ WordList
_ List
l)         = [List
l]
    lists (ArithFor String
_ List
l)      = [List
l]
    lists (Select String
_ WordList
_ List
l)      = [List
l]
    lists (Case Word
_ [CaseClause]
ccs)        = (CaseClause -> List) -> [CaseClause] -> [List]
forall a b. (a -> b) -> [a] -> [b]
map CaseClause -> List
caseClause [CaseClause]
ccs
    lists (If List
l1 List
l2 Maybe List
ml)       = List
l1 List -> [List] -> [List]
forall a. a -> [a] -> [a]
: List
l2 List -> [List] -> [List]
forall a. a -> [a] -> [a]
: Maybe List -> [List]
forall a. Maybe a -> [a]
maybeToList Maybe List
ml
    lists (Until List
l1 List
l2)       = [List
l1, List
l2]
    lists (While List
l1 List
l2)       = [List
l1, List
l2]

    caseClause :: CaseClause -> List
    caseClause :: CaseClause -> List
caseClause (CaseClause [Word]
_ List
l CaseTerm
_) = List
l

bannedCommand :: ShellCommand -> [(ShellCommand, BannedTerm)]
bannedCommand :: ShellCommand -> [(ShellCommand, BannedTerm)]
bannedCommand s :: ShellCommand
s@(SimpleCommand [] (Word
g:Word
c:[Word]
_))
  | Word
g Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
== [Char -> Span
Char Char
'g', Char -> Span
Char Char
'i', Char -> Span
Char Char
't'] Bool -> Bool -> Bool
&&
    Word
c Word -> Word -> Bool
forall a. Eq a => a -> a -> Bool
== [Char -> Span
Char Char
'c', Char -> Span
Char Char
'l', Char -> Span
Char Char
'o', Char -> Span
Char Char
'n', Char -> Span
Char Char
'e'] = [(ShellCommand
s, Text -> BanCategory -> BannedTerm
BannedTerm Text
"git" BanCategory
Downloading)]
bannedCommand s :: ShellCommand
s@(SimpleCommand [] (Word
c:[Word]
_)) = Maybe (ShellCommand, BannedTerm) -> [(ShellCommand, BannedTerm)]
forall a. Maybe a -> [a]
maybeToList (Maybe (ShellCommand, BannedTerm) -> [(ShellCommand, BannedTerm)])
-> Maybe (ShellCommand, BannedTerm) -> [(ShellCommand, BannedTerm)]
forall a b. (a -> b) -> a -> b
$ (ShellCommand
s,) (BannedTerm -> (ShellCommand, BannedTerm))
-> Maybe BannedTerm -> Maybe (ShellCommand, BannedTerm)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Word -> Maybe BannedTerm
banned Word
c
bannedCommand s :: ShellCommand
s@(SimpleCommand [Assign]
as [Word]
_) = [Assign]
as [Assign]
-> Getting
     (Endo [(ShellCommand, BannedTerm)])
     [Assign]
     (ShellCommand, BannedTerm)
-> [(ShellCommand, BannedTerm)]
forall s a. s -> Getting (Endo [a]) s a -> [a]
^.. (Assign -> Const (Endo [(ShellCommand, BannedTerm)]) Assign)
-> [Assign] -> Const (Endo [(ShellCommand, BannedTerm)]) [Assign]
forall s t a b. Each s t a b => Traversal s t a b
each ((Assign -> Const (Endo [(ShellCommand, BannedTerm)]) Assign)
 -> [Assign] -> Const (Endo [(ShellCommand, BannedTerm)]) [Assign])
-> (((ShellCommand, BannedTerm)
     -> Const
          (Endo [(ShellCommand, BannedTerm)]) (ShellCommand, BannedTerm))
    -> Assign -> Const (Endo [(ShellCommand, BannedTerm)]) Assign)
-> Getting
     (Endo [(ShellCommand, BannedTerm)])
     [Assign]
     (ShellCommand, BannedTerm)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (RValue -> Const (Endo [(ShellCommand, BannedTerm)]) RValue)
-> Assign -> Const (Endo [(ShellCommand, BannedTerm)]) Assign
Lens' Assign RValue
rValueL ((RValue -> Const (Endo [(ShellCommand, BannedTerm)]) RValue)
 -> Assign -> Const (Endo [(ShellCommand, BannedTerm)]) Assign)
-> (((ShellCommand, BannedTerm)
     -> Const
          (Endo [(ShellCommand, BannedTerm)]) (ShellCommand, BannedTerm))
    -> RValue -> Const (Endo [(ShellCommand, BannedTerm)]) RValue)
-> ((ShellCommand, BannedTerm)
    -> Const
         (Endo [(ShellCommand, BannedTerm)]) (ShellCommand, BannedTerm))
-> Assign
-> Const (Endo [(ShellCommand, BannedTerm)]) Assign
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (RValue -> [(ShellCommand, BannedTerm)])
-> SimpleGetter RValue [(ShellCommand, BannedTerm)]
forall s a. (s -> a) -> SimpleGetter s a
to RValue -> [(ShellCommand, BannedTerm)]
r Getting
  (Endo [(ShellCommand, BannedTerm)])
  RValue
  [(ShellCommand, BannedTerm)]
-> (((ShellCommand, BannedTerm)
     -> Const
          (Endo [(ShellCommand, BannedTerm)]) (ShellCommand, BannedTerm))
    -> [(ShellCommand, BannedTerm)]
    -> Const
         (Endo [(ShellCommand, BannedTerm)]) [(ShellCommand, BannedTerm)])
-> ((ShellCommand, BannedTerm)
    -> Const
         (Endo [(ShellCommand, BannedTerm)]) (ShellCommand, BannedTerm))
-> RValue
-> Const (Endo [(ShellCommand, BannedTerm)]) RValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((ShellCommand, BannedTerm)
 -> Const
      (Endo [(ShellCommand, BannedTerm)]) (ShellCommand, BannedTerm))
-> [(ShellCommand, BannedTerm)]
-> Const
     (Endo [(ShellCommand, BannedTerm)]) [(ShellCommand, BannedTerm)]
forall s t a b. Each s t a b => Traversal s t a b
each
  where
    r :: RValue -> [(ShellCommand, BannedTerm)]
r rv :: RValue
rv@(RValue Word
w) = Maybe (ShellCommand, BannedTerm) -> [(ShellCommand, BannedTerm)]
forall a. Maybe a -> [a]
maybeToList ((ShellCommand
s,) (BannedTerm -> (ShellCommand, BannedTerm))
-> Maybe BannedTerm -> Maybe (ShellCommand, BannedTerm)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Word -> Maybe BannedTerm
banned Word
w Maybe BannedTerm
-> (Maybe BannedTerm -> Maybe BannedTerm) -> Maybe BannedTerm
forall a b. a -> (a -> b) -> b
& (BannedTerm -> Identity BannedTerm)
-> Maybe BannedTerm -> Identity (Maybe BannedTerm)
forall a a'. Traversal (Maybe a) (Maybe a') a a'
_Just ((BannedTerm -> Identity BannedTerm)
 -> Maybe BannedTerm -> Identity (Maybe BannedTerm))
-> ((BanCategory -> Identity BanCategory)
    -> BannedTerm -> Identity BannedTerm)
-> (BanCategory -> Identity BanCategory)
-> Maybe BannedTerm
-> Identity (Maybe BannedTerm)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (BanCategory -> Identity BanCategory)
-> BannedTerm -> Identity BannedTerm
Lens' BannedTerm BanCategory
banCatL ((BanCategory -> Identity BanCategory)
 -> Maybe BannedTerm -> Identity (Maybe BannedTerm))
-> BanCategory -> Maybe BannedTerm -> Maybe BannedTerm
forall s t a b. ASetter s t a b -> b -> s -> t
.~ BanCategory
CleverRedirect)) [(ShellCommand, BannedTerm)]
-> [(ShellCommand, BannedTerm)] -> [(ShellCommand, BannedTerm)]
forall a. Semigroup a => a -> a -> a
<> RValue -> [(ShellCommand, BannedTerm)]
q RValue
rv
    r RValue
rv = RValue -> [(ShellCommand, BannedTerm)]
q RValue
rv

    q :: RValue -> [(ShellCommand, BannedTerm)]
    q :: RValue -> [(ShellCommand, BannedTerm)]
q RValue
rv = (BannedTerm -> (ShellCommand, BannedTerm))
-> [BannedTerm] -> [(ShellCommand, BannedTerm)]
forall a b. (a -> b) -> [a] -> [b]
map (ShellCommand
s,) ([BannedTerm] -> [(ShellCommand, BannedTerm)])
-> [BannedTerm] -> [(ShellCommand, BannedTerm)]
forall a b. (a -> b) -> a -> b
$ [Word] -> Word
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (RValue -> [Word]
rWords RValue
rv) Word -> (Span -> [BannedTerm]) -> [BannedTerm]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Span -> [BannedTerm]
p

    p :: Span -> [BannedTerm]
    p :: Span -> [BannedTerm]
p (CommandSubst String
str)   = Maybe List -> [List]
forall a. Maybe a -> [a]
maybeToList (Either ParseError List -> Maybe List
forall a b. Either a b -> Maybe b
hush (Either ParseError List -> Maybe List)
-> Either ParseError List -> Maybe List
forall a b. (a -> b) -> a -> b
$ String -> String -> Either ParseError List
parse String
"CommandSubst" String
str) [List] -> (List -> [ShellCommand]) -> [ShellCommand]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= List -> [ShellCommand]
simpleCommands [ShellCommand] -> (ShellCommand -> [BannedTerm]) -> [BannedTerm]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ((ShellCommand, BannedTerm) -> BannedTerm)
-> [(ShellCommand, BannedTerm)] -> [BannedTerm]
forall a b. (a -> b) -> [a] -> [b]
map (ShellCommand, BannedTerm) -> BannedTerm
forall a b. (a, b) -> b
snd ([(ShellCommand, BannedTerm)] -> [BannedTerm])
-> (ShellCommand -> [(ShellCommand, BannedTerm)])
-> ShellCommand
-> [BannedTerm]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShellCommand -> [(ShellCommand, BannedTerm)]
bannedCommand
    p (ArithSubst String
str)     = [Text -> BanCategory -> BannedTerm
BannedTerm (String -> Text
T.pack String
str) BanCategory
StrangeBashism]
    p (ProcessSubst ProcessSubstOp
_ String
str) = [Text -> BanCategory -> BannedTerm
BannedTerm (String -> Text
T.pack String
str) BanCategory
StrangeBashism]
    p Span
sp = [Word] -> Word
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (Span -> [Word]
sWords Span
sp) Word -> (Span -> [BannedTerm]) -> [BannedTerm]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Span -> [BannedTerm]
p

    rWords :: RValue -> [Word]
    rWords :: RValue -> [Word]
rWords (RValue Word
w)  = [Word
w]
    rWords (RArray [(Maybe Word, Word)]
ws) = [(Maybe Word, Word)]
ws [(Maybe Word, Word)] -> ((Maybe Word, Word) -> [Word]) -> [Word]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \(Maybe Word
mw, Word
w) -> Word
w Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw

    sWords :: Span -> [Word]
    sWords :: Span -> [Word]
sWords (Single Word
w)      = [Word
w]
    sWords (Double Word
w)      = [Word
w]
    sWords (ANSIC Word
w)       = [Word
w]
    sWords (Locale Word
w)      = [Word
w]
    sWords (Backquote Word
w)   = [Word
w]
    sWords (ParamSubst ParamSubst
ps) = ParamSubst -> [Word]
subWords ParamSubst
ps
    sWords Span
_               = []

    subWords :: ParamSubst -> [Word]
    subWords :: ParamSubst -> [Word]
subWords (Bare (Parameter String
_ Maybe Word
mw))                = Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Brace Bool
_ (Parameter String
_ Maybe Word
mw))             = Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Alt Bool
_ (Parameter String
_ Maybe Word
mw) Bool
_ AltOp
_ Word
w)         = Word
w Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Substring Bool
_ (Parameter String
_ Maybe Word
mw) Word
w1 Word
w2)   = Word
w1 Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Word
w2 Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Prefix String
_ Char
_)                           = []
    subWords (Indices (Parameter String
_ Maybe Word
mw))             = Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Length (Parameter String
_ Maybe Word
mw))              = Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Delete Bool
_ (Parameter String
_ Maybe Word
mw) Bool
_ Direction
_ Word
w)      = Word
w Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (Replace Bool
_ (Parameter String
_ Maybe Word
mw) Bool
_ Maybe Direction
_ Word
w1 Word
w2) = Word
w1 Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Word
w2 Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
    subWords (LetterCase Bool
_ (Parameter String
_ Maybe Word
mw) LetterCaseOp
_ Bool
_ Word
w)  = Word
w Word -> [Word] -> [Word]
forall a. a -> [a] -> [a]
: Maybe Word -> [Word]
forall a. Maybe a -> [a]
maybeToList Maybe Word
mw
bannedCommand ShellCommand
_ = []

------------
-- REPORTING
------------

-- | Dispatch different error messages depending on the category of a `BannedTerm`.
reportExploit :: BannedTerm -> (Language -> Doc AnsiStyle)
reportExploit :: BannedTerm -> Language -> Doc AnsiStyle
reportExploit (BannedTerm Text
t BanCategory
bc) = case BanCategory
bc of
  BanCategory
Downloading    -> Text -> Language -> Doc AnsiStyle
security_2 Text
t
  BanCategory
ScriptRunning  -> Text -> Language -> Doc AnsiStyle
security_3 Text
t
  BanCategory
Permissions    -> Text -> Language -> Doc AnsiStyle
security_4 Text
t
  BanCategory
InlinedBash    -> Text -> Language -> Doc AnsiStyle
security_8 Text
t
  BanCategory
StrangeBashism -> Text -> Language -> Doc AnsiStyle
security_9 Text
t
  BanCategory
CleverRedirect -> Text -> Language -> Doc AnsiStyle
security_10 Text
t

--------
-- UTILS
--------

rValueL :: Lens' Assign RValue
rValueL :: (RValue -> f RValue) -> Assign -> f Assign
rValueL RValue -> f RValue
f (Assign Parameter
p AssignOp
ao RValue
r) = Parameter -> AssignOp -> RValue -> Assign
Assign Parameter
p AssignOp
ao (RValue -> Assign) -> f RValue -> f Assign
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> RValue -> f RValue
f RValue
r