Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: Argument 1 passed to CodeIgniter\Security\Security::derandomize() must be of the type string, null given, called in /home/system/Security/Security.php on line 284
### PHP Version

7.4

### CodeIgniter4 Version

4.1.8

### CodeIgniter4 Installation Method

Manual (zip or tar.gz)

### Which operating systems have you tested for this bug?

Linux

### Which server did you use?

apache

### Database

_No response_

### What happened?

Receiving this critical error for CSRF:

CRITICAL - 2022-02-24 09:54:04 --> Argument 1 passed to CodeIgniter\Security\Security::derandomize() must be of the type string, null given, called in /home/system/Security/Security.php on line 284
#0 /home/system/Security/Security.php(284): CodeIgniter\Security\Security->derandomize(NULL)
#1 /home/system/Filters/CSRF.php(53): CodeIgniter\Security\Security->verify(Object(CodeIgniter\HTTP\IncomingRequest))
#2 /home/system/Filters/Filters.php(173): CodeIgniter\Filters\CSRF->before(Object(CodeIgniter\HTTP\IncomingRequest), NULL)
#3 /home/system/CodeIgniter.php(386): CodeIgniter\Filters\Filters->run('search', 'before')
#4 /home/system/CodeIgniter.php(320): CodeIgniter\CodeIgniter->handleRequest(NULL, Object(Config\Cache), false)
#5 /home/public/index.php(37): CodeIgniter\CodeIgniter->run()

### Steps to Reproduce

It looks like when you set public $tokenRandomize = true in Security.php config, this error occurs.

LINE: /system/Security/Security.php on line 284
$token = $this->tokenRandomize ? $this->derandomize($this->getPostedToken($request))

### Expected Output

N/A

### Anything else?

_No response_


Discussion:
I confirmed the bug when `$tokenRandomize = true` and no token posted.

```
{
    "title": "TypeError",
    "type": "TypeError",
    "code": 500,
    "message": "CodeIgniter\\Security\\Security::derandomize(): Argument #1 ($token) must be of type string, null given, called in /Users/kenji/work/codeigniter/CodeIgniter4/system/Security/Security.php on line 280",
    "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/Security/Security.php",
    "line": 362,
    "trace": [
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/Security/Security.php",
            "line": 280,
            "function": "derandomize",
            "class": "CodeIgniter\\Security\\Security",
            "type": "->",
            "args": [
                null
            ]
        },
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/Filters/CSRF.php",
            "line": 53,
            "function": "verify",
            "class": "CodeIgniter\\Security\\Security",
            "type": "->",
            "args": [
                {
                    "uri": {},
                    "config": {
                        "baseURL": "http://localhost:8080/",
                        "indexPage": "index.php",
                        "uriProtocol": "REQUEST_URI",
                        "defaultLocale": "en",
                        "negotiateLocale": false,
                        "supportedLocales": [
                            "en"
                        ],
                        "appTimezone": "America/Chicago",
                        "charset": "UTF-8",
                        "forceGlobalSecureRequests": false,
                        "sessionDriver": "CodeIgniter\\Session\\Handlers\\FileHandler",
                        "sessionCookieName": "ci_session",
                        "sessionExpiration": 7200,
                        "sessionSavePath": "/Users/kenji/work/codeigniter/CodeIgniter4/writable/session",
                        "sessionMatchIP": false,
                        "sessionTimeToUpdate": 300,
                        "sessionRegenerateDestroy": false,
                        "cookiePrefix": "",
                        "cookieDomain": "",
                        "cookiePath": "/",
                        "cookieSecure": false,
                        "cookieHTTPOnly": true,
                        "cookieSameSite": "Lax",
                        "proxyIPs": "",
                        "CSRFTokenName": "csrf_test_name",
                        "CSRFHeaderName": "X-CSRF-TOKEN",
                        "CSRFCookieName": "csrf_cookie_name",
                        "CSRFExpire": 7200,
                        "CSRFRegenerate": true,
                        "CSRFRedirect": true,
                        "CSRFSameSite": "Lax",
                        "CSPEnabled": false
                    }
                }
            ]
        },
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/Filters/Filters.php",
            "line": 173,
            "function": "before",
            "class": "CodeIgniter\\Filters\\CSRF",
            "type": "->",
            "args": [
                {
                    "uri": {},
                    "config": {
                        "baseURL": "http://localhost:8080/",
                        "indexPage": "index.php",
                        "uriProtocol": "REQUEST_URI",
                        "defaultLocale": "en",
                        "negotiateLocale": false,
                        "supportedLocales": [
                            "en"
                        ],
                        "appTimezone": "America/Chicago",
                        "charset": "UTF-8",
                        "forceGlobalSecureRequests": false,
                        "sessionDriver": "CodeIgniter\\Session\\Handlers\\FileHandler",
                        "sessionCookieName": "ci_session",
                        "sessionExpiration": 7200,
                        "sessionSavePath": "/Users/kenji/work/codeigniter/CodeIgniter4/writable/session",
                        "sessionMatchIP": false,
                        "sessionTimeToUpdate": 300,
                        "sessionRegenerateDestroy": false,
                        "cookiePrefix": "",
                        "cookieDomain": "",
                        "cookiePath": "/",
                        "cookieSecure": false,
                        "cookieHTTPOnly": true,
                        "cookieSameSite": "Lax",
                        "proxyIPs": "",
                        "CSRFTokenName": "csrf_test_name",
                        "CSRFHeaderName": "X-CSRF-TOKEN",
                        "CSRFCookieName": "csrf_cookie_name",
                        "CSRFExpire": 7200,
                        "CSRFRegenerate": true,
                        "CSRFRedirect": true,
                        "CSRFSameSite": "Lax",
                        "CSPEnabled": false
                    }
                },
                null
            ]
        },
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/CodeIgniter.php",
            "line": 434,
            "function": "run",
            "class": "CodeIgniter\\Filters\\Filters",
            "type": "->",
            "args": [
                "/",
                "before"
            ]
        },
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/CodeIgniter.php",
            "line": 344,
            "function": "handleRequest",
            "class": "CodeIgniter\\CodeIgniter",
            "type": "->",
            "args": [
                null,
                {
                    "handler": "file",
                    "backupHandler": "dummy",
                    "storePath": "/Users/kenji/work/codeigniter/CodeIgniter4/writable/cache/",
                    "cacheQueryString": false,
                    "prefix": "",
                    "ttl": 60,
                    "reservedCharacters": "{}()/\\@:",
                    "file": {
                        "storePath": "/Users/kenji/work/codeigniter/CodeIgniter4/writable/cache/",
                        "mode": 416
                    },
                    "memcached": {
                        "host": "127.0.0.1",
                        "port": 11211,
                        "weight": 1,
                        "raw": false
                    },
                    "redis": {
                        "host": "127.0.0.1",
                        "password": null,
                        "port": 6379,
                        "timeout": 0,
                        "database": 0
                    },
                    "validHandlers": {
                        "dummy": "CodeIgniter\\Cache\\Handlers\\DummyHandler",
                        "file": "CodeIgniter\\Cache\\Handlers\\FileHandler",
                        "memcached": "CodeIgniter\\Cache\\Handlers\\MemcachedHandler",
                        "predis": "CodeIgniter\\Cache\\Handlers\\PredisHandler",
                        "redis": "CodeIgniter\\Cache\\Handlers\\RedisHandler",
                        "wincache": "CodeIgniter\\Cache\\Handlers\\WincacheHandler"
                    }
                },
                false
            ]
        },
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/public/index.php",
            "line": 40,
            "function": "run",
            "class": "CodeIgniter\\CodeIgniter",
            "type": "->",
            "args": []
        },
        {
            "file": "/Users/kenji/work/codeigniter/CodeIgniter4/system/Commands/Server/rewrite.php",
            "line": 43,
            "args": [
                "/Users/kenji/work/codeigniter/CodeIgniter4/public/index.php"
            ],
            "function": "require_once"
        }
    ]
}
```




Patch:
diff --git a/system/Security/Security.php b/system/Security/Security.php
index b9309797f580..3adb7bf68857 100644
--- a/system/Security/Security.php
+++ b/system/Security/Security.php
@@ -277,8 +277,9 @@ public function verify(RequestInterface $request)
             return $this;
         }
 
-        $token = $this->tokenRandomize ? $this->derandomize($this->getPostedToken($request))
-            : $this->getPostedToken($request);
+        $postedToken = $this->getPostedToken($request);
+        $token       = ($postedToken !== null && $this->tokenRandomize)
+            ? $this->derandomize($postedToken) : $postedToken;
 
         // Do the tokens match?
         if (! isset($token, $this->hash) || ! hash_equals($this->hash, $token)) {


Test patch:
diff --git a/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php b/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php
index ea416f896a96..97b3330e8f24 100644
--- a/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php
+++ b/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php
@@ -108,6 +108,21 @@ public function testHashIsReadFromSession()
         );
     }
 
+    public function testCSRFVerifyPostNoToken()
+    {
+        $this->expectException(SecurityException::class);
+        $this->expectExceptionMessage('The action you requested is not allowed.');
+
+        $_SERVER['REQUEST_METHOD'] = 'POST';
+        unset($_POST['csrf_test_name']);
+
+        $request = new IncomingRequest(new MockAppConfig(), new URI('http://badurl.com'), null, new UserAgent());
+
+        $security = new Security(new MockAppConfig());
+
+        $security->verify($request);
+    }
+
     public function testCSRFVerifyPostThrowsExceptionOnNoMatch()
     {
         $this->expectException(SecurityException::class);