Yes, in the past there have been lots of exploits that only relied on malicious HTML and CSS code.
You are right in that parsing a complex, turing-complete language is potentially more error-prone, giving an attacker more tools to craft an exploit.
Yet, there are many different ways in which the implementation of the used CSS parser or other modules involved in processing the code can be vulnerable.
As an example, take CVE-2010-2752
- Mozilla Firefox CSS font-face Remote Code Execution Vulnerability. This vulnerability could be triggered simply by creating a website with specially crafted CSS font-face
rules and yet it had the potential to compromise the host.
The flawed code was located in Mozilla's CSS parser and had a trivial bug: When allocating memory to store the font-face
references, a 16-bit integer was used for the index. However, when the actual values were filled in, a 32-bit integer was used instead. This inconsistency led to an integer overflow when a stylesheet supplied an exessive number of external font references. Consequently, an attacker could write to unexpected memory locations and turn the index overflow into an arbitrary code execution exploit.
Since most of the Mozilla core is written in C++, there is no built-in overflow protection and developers are in charge of dealing securely with the memory. A browser written in Python would likely face very different types of vulnerabilities (and also be painfully slow).