Gennaro Gattuso’s side maintained their place in the top four even though Krzysztof Piatek’s 26th-minute strike was cancelled out by 19-year-old Zaniolo’s effort after the interval. He became the youngest player to score three goals for Roma in Serie A since Francesco Totti in 1995.
Piatek scored his first league goal for Milan after joining from Genoa in the January transfer window.
Sections of the Stadio Olimpico crowd held protests against their own team after a week in which Roma surrendered a three-goal lead to draw with Atalanta in Serie A and suffered a humiliating 7-1 Coppa Italia defeat by Fiorentina.
Milan’s two winter signings combined for the opening goal as Brazilian midfielder Lucas Paqueta robbed the ball off Lorenzo Pellegrini before his low cross was dispatched by Piatek from close range.
An inspired performance from goalkeeper Gianluigi Donnarumma kept Milan in front until the break, but there was nothing he could do to stop Zaniolo smashing his finish in from close range within a minute of the second half kicking off after he parried Mateo Musacchio’s goal-bound deflection into the path of the midfielder.
Roma continued to apply pressure but could not find a way past Donnarumma, who showed sharp reflexes to deny Edin Dzeko with 20 minutes remaining, before Pellegrini’s header hit the post as Milan weathered the storm.
The result leaves AC Milan fourth, and the final Champions League place, with 36 points from 22 games. Roma are one point behind in fifth.